// license:BSD-3-Clause // copyright-holders:Wilbert Pol /*************************************************************************** Z80 CTC (Z8430) implementation based on original version (c) 1997, Tatsuyuki Satoh ***************************************************************************/ #include "emu.h" #include "z80ctc.h" //************************************************************************** // DEBUGGING //************************************************************************** #define VERBOSE 0 #include "logmacro.h" //************************************************************************** // CONSTANTS //************************************************************************** // these are the bits of the incoming commands to the CTC constexpr u16 INTERRUPT = 0x80; constexpr u16 INTERRUPT_ON = 0x80; constexpr u16 INTERRUPT_OFF = 0x00; constexpr u16 MODE = 0x40; constexpr u16 MODE_TIMER = 0x00; constexpr u16 MODE_COUNTER = 0x40; constexpr u16 PRESCALER = 0x20; //constexpr u16 PRESCALER_256 = 0x20; constexpr u16 PRESCALER_16 = 0x00; constexpr u16 EDGE = 0x10; constexpr u16 EDGE_FALLING = 0x00; constexpr u16 EDGE_RISING = 0x10; constexpr u16 TRIGGER = 0x08; constexpr u16 TRIGGER_AUTO = 0x00; //constexpr u16 TRIGGER_CLOCK = 0x08; constexpr u16 CONSTANT = 0x04; constexpr u16 CONSTANT_LOAD = 0x04; //constexpr u16 CONSTANT_NONE = 0x00; constexpr u16 RESET = 0x02; //constexpr u16 RESET_CONTINUE = 0x00; constexpr u16 RESET_ACTIVE = 0x02; constexpr u16 CONTROL = 0x01; constexpr u16 CONTROL_VECTOR = 0x00; constexpr u16 CONTROL_WORD = 0x01; // these extra bits help us keep things accurate constexpr u16 WAITING_FOR_TRIG = 0x100; //************************************************************************** // LIVE DEVICE //************************************************************************** // device type definitions DEFINE_DEVICE_TYPE(Z80CTC, z80ctc_device, "z80ctc", "Z80 CTC") DEFINE_DEVICE_TYPE(Z80CTC_CHANNEL, z80ctc_channel_device, "z80ctc_channel", "Z80 CTC Channel") //------------------------------------------------- // z80ctc_device - constructor //------------------------------------------------- z80ctc_device::z80ctc_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, Z80CTC, tag, owner, clock), device_z80daisy_interface(mconfig, *this), m_intr_cb(*this), m_zc_cb{*this, *this, *this, *this}, m_vector(0), m_channel(*this, "ch%u", 0U) { } //------------------------------------------------- // read - standard handler for reading //------------------------------------------------- READ8_MEMBER( z80ctc_device::read ) { return m_channel[offset & 3]->read(); } //------------------------------------------------- // write - standard handler for writing //------------------------------------------------- WRITE8_MEMBER( z80ctc_device::write ) { m_channel[offset & 3]->write(data); } //------------------------------------------------- // trg0-3 - standard write line handlers for each // trigger //------------------------------------------------- WRITE_LINE_MEMBER( z80ctc_device::trg0 ) { m_channel[0]->trigger(state != 0); } WRITE_LINE_MEMBER( z80ctc_device::trg1 ) { m_channel[1]->trigger(state != 0); } WRITE_LINE_MEMBER( z80ctc_device::trg2 ) { m_channel[2]->trigger(state != 0); } WRITE_LINE_MEMBER( z80ctc_device::trg3 ) { m_channel[3]->trigger(state != 0); } //------------------------------------------------- // device_add_mconfig - add device-specific // machine configuration //------------------------------------------------- MACHINE_CONFIG_START(z80ctc_device::device_add_mconfig) MCFG_DEVICE_ADD("ch0", Z80CTC_CHANNEL, 0) MCFG_DEVICE_ADD("ch1", Z80CTC_CHANNEL, 0) MCFG_DEVICE_ADD("ch2", Z80CTC_CHANNEL, 0) MCFG_DEVICE_ADD("ch3", Z80CTC_CHANNEL, 0) MACHINE_CONFIG_END //------------------------------------------------- // device_resolve_objects - resolve objects that // may be needed for other devices to set // initial conditions at start time //------------------------------------------------- void z80ctc_device::device_resolve_objects() { // resolve callbacks m_intr_cb.resolve_safe(); for (int ch = 0; ch < 4; ch++) { m_zc_cb[ch].resolve_safe(); // assign channel index m_channel[ch]->m_index = ch; } } //------------------------------------------------- // device_start - device-specific startup //------------------------------------------------- void z80ctc_device::device_start() { // register for save states save_item(NAME(m_vector)); } //------------------------------------------------- // device_reset - device-specific reset //------------------------------------------------- void z80ctc_device::device_reset_after_children() { // check for interrupts interrupt_check(); LOG("CTC Reset\n"); } //************************************************************************** // DAISY CHAIN INTERFACE //************************************************************************** //------------------------------------------------- // z80daisy_irq_state - return the overall IRQ // state for this device //------------------------------------------------- int z80ctc_device::z80daisy_irq_state() { LOG("CTC IRQ state = %d%d%d%d\n", m_channel[0]->m_int_state, m_channel[1]->m_int_state, m_channel[2]->m_int_state, m_channel[3]->m_int_state); // loop over all channels int state = 0; for (int ch = 0; ch < 4; ch++) { // if we're servicing a request, don't indicate more interrupts if (m_channel[ch]->m_int_state & Z80_DAISY_IEO) { state |= Z80_DAISY_IEO; break; } state |= m_channel[ch]->m_int_state; } return state; } //------------------------------------------------- // z80daisy_irq_ack - acknowledge an IRQ and // return the appropriate vector //------------------------------------------------- int z80ctc_device::z80daisy_irq_ack() { // loop over all channels for (int ch = 0; ch < 4; ch++) { z80ctc_channel_device &channel = *m_channel[ch]; // find the first channel with an interrupt requested if (channel.m_int_state & Z80_DAISY_INT) { LOG("CTC IRQAck ch%d\n", ch); // clear interrupt, switch to the IEO state, and update the IRQs channel.m_int_state = Z80_DAISY_IEO; interrupt_check(); return m_vector + ch * 2; } } //logerror("z80ctc_irq_ack: failed to find an interrupt to ack!\n"); return m_vector; } //------------------------------------------------- // z80daisy_irq_reti - clear the interrupt // pending state to allow other interrupts through //------------------------------------------------- void z80ctc_device::z80daisy_irq_reti() { // loop over all channels for (int ch = 0; ch < 4; ch++) { z80ctc_channel_device &channel = *m_channel[ch]; // find the first channel with an IEO pending if (channel.m_int_state & Z80_DAISY_IEO) { LOG("CTC IRQReti ch%d\n", ch); // clear the IEO state and update the IRQs channel.m_int_state &= ~Z80_DAISY_IEO; interrupt_check(); return; } } //logerror("z80ctc_irq_reti: failed to find an interrupt to clear IEO on!\n"); } //************************************************************************** // INTERNAL STATE MANAGEMENT //************************************************************************** //------------------------------------------------- // interrupt_check - look for pending interrupts // and update the line //------------------------------------------------- void z80ctc_device::interrupt_check() { int state = (z80daisy_irq_state() & Z80_DAISY_INT) ? ASSERT_LINE : CLEAR_LINE; m_intr_cb(state); } //************************************************************************* // CTC CHANNELS //************************************************************************** //------------------------------------------------- // z80ctc_channel_device - constructor //------------------------------------------------- z80ctc_channel_device::z80ctc_channel_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock) : device_t(mconfig, Z80CTC_CHANNEL, tag, owner, clock), m_device(*this, DEVICE_SELF_OWNER), m_index(0), m_mode(0), m_tconst(0), m_down(0), m_extclk(0), m_timer(nullptr), m_int_state(0) { } //------------------------------------------------- // device_start - set up at device start time //------------------------------------------------- void z80ctc_channel_device::device_start() { // initialize state m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(z80ctc_channel_device::timer_callback), this)); // register for save states save_item(NAME(m_mode)); save_item(NAME(m_tconst)); save_item(NAME(m_down)); save_item(NAME(m_extclk)); save_item(NAME(m_int_state)); } //------------------------------------------------- // device_reset - reset the channel //------------------------------------------------- void z80ctc_channel_device::device_reset() { m_mode = RESET_ACTIVE; m_tconst = 0x100; m_timer->adjust(attotime::never); m_int_state = 0; } //------------------------------------------------- // period - return the current channel's period //------------------------------------------------- attotime z80ctc_channel_device::period() const { // if reset active, no period if ((m_mode & RESET) == RESET_ACTIVE) return attotime::zero; // if counter mode, no real period if ((m_mode & MODE) == MODE_COUNTER) { logerror("CounterMode : Can't calculate period\n"); return attotime::zero; } // compute the period attotime period = m_device->clocks_to_attotime((m_mode & PRESCALER) == PRESCALER_16 ? 16 : 256); return period * m_tconst; } //------------------------------------------------- // read - read the channel's state //------------------------------------------------- u8 z80ctc_channel_device::read() { // if we're in counter mode, just return the count if ((m_mode & MODE) == MODE_COUNTER || (m_mode & WAITING_FOR_TRIG)) return m_down; // else compute the down counter value else { attotime period = m_device->clocks_to_attotime((m_mode & PRESCALER) == PRESCALER_16 ? 16 : 256); LOG("CTC clock %f\n",ATTOSECONDS_TO_HZ(period.attoseconds())); if (m_timer != nullptr) return ((int)(m_timer->remaining().as_double() / period.as_double()) + 1) & 0xff; else return 0; } } //------------------------------------------------- // write - handle writes to a channel //------------------------------------------------- void z80ctc_channel_device::write(u8 data) { // if we're waiting for a time constant, this is it if ((m_mode & CONSTANT) == CONSTANT_LOAD) { LOG("Time constant = %02x\n", data); // set the time constant (0 -> 0x100) m_tconst = data ? data : 0x100; // clear the internal mode -- we're no longer waiting m_mode &= ~CONSTANT; // also clear the reset, since the constant gets it going again m_mode &= ~RESET; // if we're in timer mode.... if ((m_mode & MODE) == MODE_TIMER) { // if we're triggering on the time constant, reset the down counter now if ((m_mode & TRIGGER) == TRIGGER_AUTO) { attotime curperiod = period(); m_timer->adjust(curperiod, 0, curperiod); } // else set the bit indicating that we're waiting for the appropriate trigger else m_mode |= WAITING_FOR_TRIG; } // also set the down counter in case we're clocking externally m_down = m_tconst; } // if we're writing the interrupt vector, handle it specially #if 0 /* Tatsuyuki Satoh changes */ // The 'Z80family handbook' wrote, // interrupt vector is able to set for even channel (0 or 2) else if ((data & CONTROL) == CONTROL_VECTOR && (m_index & 1) == 0) #else else if ((data & CONTROL) == CONTROL_VECTOR && m_index == 0) #endif { m_device->m_vector = data & 0xf8; LOG("Vector = %02x\n", m_device->m_vector); } // this must be a control word else if ((data & CONTROL) == CONTROL_WORD) { // (mode change without reset?) if ((m_mode & MODE) == MODE_TIMER && (data & MODE) == MODE_COUNTER && (data & RESET) == 0) { m_timer->adjust(attotime::never); } // set the new mode m_mode = data; LOG("Channel mode = %02x\n", data); // clearing this bit resets the interrupt state regardless of M1 activity (or lack thereof) if ((data & INTERRUPT) == INTERRUPT_OFF && (m_int_state & Z80_DAISY_INT)) { m_int_state &= ~Z80_DAISY_INT; LOG("Interrupt forced off\n"); m_device->interrupt_check(); } // if we're being reset, clear out any pending timers for this channel if ((data & RESET) == RESET_ACTIVE) { m_timer->adjust(attotime::never); // note that we don't clear the interrupt state here! } } } //------------------------------------------------- // trigger - clock this channel and handle any // side-effects //------------------------------------------------- void z80ctc_channel_device::trigger(bool state) { // see if the trigger value has changed if (state != m_extclk) { m_extclk = state; // see if this is the active edge of the trigger if (((m_mode & EDGE) == EDGE_RISING && state) || ((m_mode & EDGE) == EDGE_FALLING && !state)) { // if we're waiting for a trigger, start the timer if ((m_mode & WAITING_FOR_TRIG) && (m_mode & MODE) == MODE_TIMER) { attotime curperiod = period(); LOG("Period = %s\n", curperiod.as_string()); m_timer->adjust(curperiod, 0, curperiod); } // we're no longer waiting m_mode &= ~WAITING_FOR_TRIG; // if we're clocking externally, decrement the count if ((m_mode & MODE) == MODE_COUNTER) { // if we hit zero, do the same thing as for a timer interrupt if (--m_down == 0) timer_callback(nullptr,0); } } } } //------------------------------------------------- // trigger - clock this channel and handle any // side-effects //------------------------------------------------- TIMER_CALLBACK_MEMBER(z80ctc_channel_device::timer_callback) { // down counter has reached zero - see if we should interrupt if ((m_mode & INTERRUPT) == INTERRUPT_ON) { m_int_state |= Z80_DAISY_INT; LOG("Timer interrupt\n"); m_device->interrupt_check(); } // generate the clock pulse // FIXME: should only be cleared after one cycle of the channel input clock m_device->m_zc_cb[m_index](1); m_device->m_zc_cb[m_index](0); // reset the down counter m_down = m_tconst; }