summaryrefslogtreecommitdiffstatshomepage
path: root/src/devices/machine/z80sio.cpp
diff options
context:
space:
mode:
author Vas Crabb <vas@vastheman.com>2017-11-15 03:41:39 +1100
committer Vas Crabb <vas@vastheman.com>2017-11-16 22:22:20 +1100
commit46c4dfb5f91509491306e9d502eea3a290ba951b (patch)
treecdc5b1afc8dbdc9707e8650983c802de00c5484f /src/devices/machine/z80sio.cpp
parent4c9bac79ff0cf7aa1b87048485ed8ea77dea8051 (diff)
z80sio updates:
* Don't use device_serial_interface for transmit - it can't support sync modes, on-the-fly register updates, and other weirdness. * Better modelling of 1-deep transmit queue. * Better RTS/CTS behaviour. * Completely overhauled interrupt logic - vectors should be correct for most async modes. * Implemented different auto-reset receive errors in MPSC vs SIO. * Implemented SDLC transmission including bit stuffing, transmit CRC, abort, and underrun/end-of-message behaviour. Added an SDLC consumer device that logs SNA frame headers and data.
Diffstat (limited to 'src/devices/machine/z80sio.cpp')
-rw-r--r--src/devices/machine/z80sio.cpp1457
1 files changed, 923 insertions, 534 deletions
diff --git a/src/devices/machine/z80sio.cpp b/src/devices/machine/z80sio.cpp
index ef4fa943116..64208d4db8f 100644
--- a/src/devices/machine/z80sio.cpp
+++ b/src/devices/machine/z80sio.cpp
@@ -50,6 +50,9 @@
#include "emu.h"
#include "z80sio.h"
+#include "machine/sdlc.h"
+
+
//**************************************************************************
// MACROS / CONSTANTS
//**************************************************************************
@@ -66,8 +69,8 @@
#define LOG_SYNC (1U << 9)
#define LOG_BIT (1U << 10)
-//#define VERBOSE (LOG_INT|LOG_READ|LOG_SETUP|LOG_TX|LOG_CMD) //(LOG_SETUP|LOG_INT|LOG_CMD|LOG_DCD|LOG_CTS|LOG_TX)
-//#define LOG_OUTPUT_FUNC printf
+//#define VERBOSE (LOG_INT | LOG_READ | LOG_SETUP | LOG_TX | LOG_CMD | LOG_CTS)
+//#define LOG_OUTPUT_STREAM std::cout
#include "logmacro.h"
@@ -91,15 +94,150 @@
#define CHANA_TAG "cha"
#define CHANB_TAG "chb"
+
+enum : uint8_t
+{
+ RR0_RX_CHAR_AVAILABLE = 0x01,
+ RR0_INTERRUPT_PENDING = 0x02,
+ RR0_TX_BUFFER_EMPTY = 0x04,
+ RR0_DCD = 0x08,
+ RR0_SYNC_HUNT = 0x10,
+ RR0_CTS = 0x20,
+ RR0_TX_UNDERRUN = 0x40,
+ RR0_BREAK_ABORT = 0x80
+};
+
+enum : uint8_t
+{
+ RR1_ALL_SENT = 0x01,
+ RR1_RESIDUE_CODE_MASK = 0x0e,
+ RR1_PARITY_ERROR = 0x10,
+ RR1_RX_OVERRUN_ERROR = 0x20,
+ RR1_CRC_FRAMING_ERROR = 0x40,
+ RR1_END_OF_FRAME = 0x80
+};
+
+enum : uint8_t
+{
+ RR2_INT_VECTOR_MASK = 0xff,
+ RR2_INT_VECTOR_V1 = 0x02,
+ RR2_INT_VECTOR_V2 = 0x04,
+ RR2_INT_VECTOR_V3 = 0x08
+};
+
+enum : uint8_t
+{
+ WR0_REGISTER_MASK = 0x07,
+ WR0_COMMAND_MASK = 0x38,
+ WR0_NULL = 0x00,
+ WR0_SEND_ABORT = 0x08,
+ WR0_RESET_EXT_STATUS = 0x10,
+ WR0_CHANNEL_RESET = 0x18,
+ WR0_ENABLE_INT_NEXT_RX = 0x20,
+ WR0_RESET_TX_INT = 0x28,
+ WR0_ERROR_RESET = 0x30,
+ WR0_RETURN_FROM_INT = 0x38,
+ WR0_CRC_RESET_CODE_MASK = 0xc0,
+ WR0_CRC_RESET_NULL = 0x00,
+ WR0_CRC_RESET_RX = 0x40,
+ WR0_CRC_RESET_TX = 0x80,
+ WR0_CRC_RESET_TX_UNDERRUN = 0xc0
+};
+
+enum : uint8_t
+{
+ WR1_EXT_INT_ENABLE = 0x01,
+ WR1_TX_INT_ENABLE = 0x02,
+ WR1_STATUS_VECTOR = 0x04,
+ WR1_RX_INT_MODE_MASK = 0x18,
+ WR1_RX_INT_DISABLE = 0x00,
+ WR1_RX_INT_FIRST = 0x08,
+ WR1_RX_INT_ALL_PARITY = 0x10,
+ WR1_RX_INT_ALL = 0x18,
+ WR1_WRDY_ON_RX_TX = 0x20, // not supported
+ WR1_WRDY_FUNCTION = 0x40, // not supported
+ WR1_WRDY_ENABLE = 0x80 // not supported
+};
+
+enum : uint8_t
+{
+ WR2_DATA_XFER_INT = 0x00, // not supported
+ WR2_DATA_XFER_DMA_INT = 0x01, // not supported
+ WR2_DATA_XFER_DMA = 0x02, // not supported
+ WR2_DATA_XFER_ILLEGAL = 0x03, // not supported
+ WR2_DATA_XFER_MASK = 0x03, // not supported
+ WR2_PRIORITY = 0x04,
+ WR2_MODE_8085_1 = 0x00, // not supported
+ WR2_MODE_8085_2 = 0x08, // not supported
+ WR2_MODE_8086_8088 = 0x10, // not supported
+ WR2_MODE_ILLEGAL = 0x18, // not supported
+ WR2_MODE_MASK = 0x18, // not supported
+ WR2_VECTORED_INT = 0x20, // partially supported
+ WR2_PIN10_SYNDETB_RTSB = 0x80 // not supported
+};
+
+enum : uint8_t
+{
+ WR3_RX_ENABLE = 0x01,
+ WR3_SYNC_CHAR_LOAD_INHIBIT= 0x02, // not supported
+ WR3_ADDRESS_SEARCH_MODE = 0x04, // not supported
+ WR3_RX_CRC_ENABLE = 0x08, // not supported
+ WR3_ENTER_HUNT_PHASE = 0x10, // partially supported
+ WR3_AUTO_ENABLES = 0x20,
+ WR3_RX_WORD_LENGTH_MASK = 0xc0,
+ WR3_RX_WORD_LENGTH_5 = 0x00,
+ WR3_RX_WORD_LENGTH_7 = 0x40,
+ WR3_RX_WORD_LENGTH_6 = 0x80,
+ WR3_RX_WORD_LENGTH_8 = 0xc0
+};
+
+enum : uint8_t
+{
+ WR4_PARITY_ENABLE = 0x01,
+ WR4_PARITY_EVEN = 0x02,
+ WR4_STOP_BITS_MASK = 0x0c,
+ WR4_STOP_BITS_SYNC = 0x00, // partially supported
+ WR4_STOP_BITS_1 = 0x04,
+ WR4_STOP_BITS_1_5 = 0x08,
+ WR4_STOP_BITS_2 = 0x0c,
+ WR4_SYNC_MODE_MASK = 0x30, // partially supported
+ WR4_SYNC_MODE_8_BIT = 0x00, // not supported
+ WR4_SYNC_MODE_16_BIT = 0x10, // not supported
+ WR4_SYNC_MODE_SDLC = 0x20, // partially supported
+ WR4_SYNC_MODE_EXT = 0x30, // not supported
+ WR4_CLOCK_RATE_MASK = 0xc0,
+ WR4_CLOCK_RATE_X1 = 0x00,
+ WR4_CLOCK_RATE_X16 = 0x40,
+ WR4_CLOCK_RATE_X32 = 0x80,
+ WR4_CLOCK_RATE_X64 = 0xc0
+};
+
+enum : uint8_t
+{
+ WR5_TX_CRC_ENABLE = 0x01,
+ WR5_RTS = 0x02,
+ WR5_CRC16 = 0x04,
+ WR5_TX_ENABLE = 0x08,
+ WR5_SEND_BREAK = 0x10,
+ WR5_TX_WORD_LENGTH_MASK = 0x60,
+ WR5_TX_WORD_LENGTH_5 = 0x00,
+ WR5_TX_WORD_LENGTH_6 = 0x40,
+ WR5_TX_WORD_LENGTH_7 = 0x20,
+ WR5_TX_WORD_LENGTH_8 = 0x60,
+ WR5_DTR = 0x80
+};
+
+
//**************************************************************************
// DEVICE DEFINITIONS
//**************************************************************************
// device type definition
-DEFINE_DEVICE_TYPE(Z80SIO, z80sio_device, "z80sio", "Z80 SIO")
DEFINE_DEVICE_TYPE(Z80SIO_CHANNEL, z80sio_channel, "z80sio_channel", "Z80 SIO channel")
-DEFINE_DEVICE_TYPE(UPD7201_NEW, upd7201_new_device, "upd7201_new", "NEC uPD7201 MPSC (new)") // Remove trailing N when z80dart.cpp's 7201 implementation is fully replaced
+DEFINE_DEVICE_TYPE(I8274_CHANNEL, i8274_channel, "i8274_channel", "Intel 8274 MPSC channel")
+DEFINE_DEVICE_TYPE(Z80SIO, z80sio_device, "z80sio", "Z80 SIO")
DEFINE_DEVICE_TYPE(I8274_NEW, i8274_new_device, "i8274_new", "Intel 8274 MPSC (new)") // Remove trailing N when z80dart.cpp's 8274 implementation is fully replaced
+DEFINE_DEVICE_TYPE(UPD7201_NEW, upd7201_new_device, "upd7201_new", "NEC uPD7201 MPSC (new)") // Remove trailing N when z80dart.cpp's 7201 implementation is fully replaced
//-------------------------------------------------
// device_add_mconfig - add device configuration
@@ -109,16 +247,81 @@ MACHINE_CONFIG_MEMBER( z80sio_device::device_add_mconfig )
MCFG_DEVICE_ADD(CHANB_TAG, Z80SIO_CHANNEL, 0)
MACHINE_CONFIG_END
+MACHINE_CONFIG_MEMBER( i8274_new_device::device_add_mconfig )
+ MCFG_DEVICE_ADD(CHANA_TAG, I8274_CHANNEL, 0)
+ MCFG_DEVICE_ADD(CHANB_TAG, I8274_CHANNEL, 0)
+MACHINE_CONFIG_END
+
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
+inline void z80sio_channel::out_txd_cb(int state)
+{
+ ((m_index == z80sio_device::CHANNEL_A) ? m_uart->m_out_txda_cb : m_uart->m_out_txdb_cb)(state);
+}
+
+inline void z80sio_channel::out_rts_cb(int state)
+{
+ ((m_index == z80sio_device::CHANNEL_A) ? m_uart->m_out_rtsa_cb : m_uart->m_out_rtsb_cb)(state);
+}
+
+inline void z80sio_channel::out_dtr_cb(int state)
+{
+ ((m_index == z80sio_device::CHANNEL_A) ? m_uart->m_out_dtra_cb : m_uart->m_out_dtrb_cb)(state);
+}
+
+inline bool z80sio_channel::transmit_allowed() const
+{
+ return (m_wr5 & WR5_TX_ENABLE) && (!(m_wr3 & WR3_AUTO_ENABLES) || !m_cts);
+}
+
+inline void z80sio_channel::set_rts(int state)
+{
+ if (bool(m_rts) != bool(state))
+ {
+ LOG("%s(%d) \"%s\" Channel %c \n", FUNCNAME, state, owner()->tag(), 'A' + m_index);
+ out_rts_cb(m_rts = state);
+ }
+}
+
+inline void z80sio_channel::set_dtr(int state)
+{
+ if (bool(m_dtr) != bool(state))
+ {
+ LOG("%s(%d) \"%s\" Channel %c \n", FUNCNAME, state, owner()->tag(), 'A' + m_index);
+ out_dtr_cb(m_dtr = state);
+ }
+}
+
+inline void z80sio_channel::tx_setup(uint16_t data, int bits, int parity, int framing)
+{
+ // TODO: what does enabling parity in SDLC mode do on a real chip?
+ m_tx_bits = bits;
+ m_tx_parity = parity;
+ m_tx_framing = framing;
+ m_tx_sr = data | (~uint16_t(0) << bits);
+ if (parity)
+ {
+ if (m_wr4 & WR4_PARITY_EVEN)
+ m_tx_sr &= ~(uint16_t(1) << m_tx_bits);
+ ++m_tx_bits;
+ }
+}
+
+inline void z80sio_channel::tx_setup_flag()
+{
+ // SDLC transmit examples don't show flag being loaded, implying it's hard-coded on the transmit side
+ tx_setup(0x7e, 8, 0, 1);
+}
+
+
//-------------------------------------------------
// z80sio_device - constructor
//-------------------------------------------------
-z80sio_device::z80sio_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t variant)
- : device_t(mconfig, type, tag, owner, clock),
+z80sio_device::z80sio_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
+ device_t(mconfig, type, tag, owner, clock),
device_z80daisy_interface(mconfig, *this),
m_chanA(*this, CHANA_TAG),
m_chanB(*this, CHANB_TAG),
@@ -137,25 +340,29 @@ z80sio_device::z80sio_device(const machine_config &mconfig, device_type type, co
m_out_txdrqa_cb(*this),
m_out_rxdrqb_cb(*this),
m_out_txdrqb_cb(*this),
- m_variant(variant),
m_cputag(nullptr)
{
for (auto & elem : m_int_state)
elem = 0;
}
-z80sio_device::z80sio_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : z80sio_device(mconfig, Z80SIO, tag, owner, clock, TYPE_Z80SIO)
+z80sio_device::z80sio_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ z80sio_device(mconfig, Z80SIO, tag, owner, clock)
{
}
-upd7201_new_device::upd7201_new_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : z80sio_device(mconfig, UPD7201_NEW, tag, owner, clock, TYPE_UPD7201)
+i8274_new_device::i8274_new_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock) :
+ z80sio_device(mconfig, type, tag, owner, clock)
{
}
-i8274_new_device::i8274_new_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : z80sio_device(mconfig, I8274_NEW, tag, owner, clock, TYPE_I8274)
+i8274_new_device::i8274_new_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ i8274_new_device(mconfig, I8274_NEW, tag, owner, clock)
+{
+}
+
+upd7201_new_device::upd7201_new_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
+ i8274_new_device(mconfig, UPD7201_NEW, tag, owner, clock)
{
}
@@ -193,7 +400,6 @@ void z80sio_device::device_start()
// state saving
save_item(NAME(m_int_state));
- save_item(NAME(m_int_source));
}
@@ -203,9 +409,6 @@ void z80sio_device::device_start()
void z80sio_device::device_reset()
{
LOG("%s \"%s\" \n", FUNCNAME, tag());
-
- m_chanA->reset();
- m_chanB->reset();
}
//-------------------------------------------------
@@ -213,26 +416,26 @@ void z80sio_device::device_reset()
//-------------------------------------------------
int z80sio_device::z80daisy_irq_state()
{
- int state = 0;
+ int const *const prio = interrupt_priorities();
LOGINT("%s %s Hi->Lo:%d%d%d%d%d%d ", tag(), FUNCNAME,
- m_int_state[0], m_int_state[1], m_int_state[2],
- m_int_state[3], m_int_state[4], m_int_state[5]);
+ m_int_state[prio[0]], m_int_state[prio[1]], m_int_state[prio[2]],
+ m_int_state[prio[3]], m_int_state[prio[4]], m_int_state[prio[5]]);
// loop over all interrupt sources
- for (auto & elem : m_int_state)
+ int state = 0;
+ for (int i = 0; ARRAY_LENGTH(m_int_state) > i; ++i)
{
// if we're servicing a request, don't indicate more interrupts
- if (elem & Z80_DAISY_IEO)
+ if (m_int_state[prio[i]] & Z80_DAISY_IEO)
{
state |= Z80_DAISY_IEO;
break;
}
- state |= elem;
+ state |= m_int_state[prio[i]];
}
LOGINT("Interrupt State %u\n", state);
-
return state;
}
@@ -242,34 +445,105 @@ int z80sio_device::z80daisy_irq_state()
//-------------------------------------------------
int z80sio_device::z80daisy_irq_ack()
{
- int ret = -1; // Indicate default vector
+ LOGINT("%s \n", FUNCNAME);
- LOGINT("%s %s \n",tag(), FUNCNAME);
// loop over all interrupt sources
- for (auto & elem : m_int_state)
+ int const *const prio = interrupt_priorities();
+ for (int i = 0; ARRAY_LENGTH(m_int_state) > i; ++i)
{
// find the first channel with an interrupt requested
- if (elem & Z80_DAISY_INT)
+ if (m_int_state[prio[i]] & Z80_DAISY_INT)
{
- elem = Z80_DAISY_IEO; // Set IUS bit (called IEO in z80 daisy lingo)
- m_chanA->m_rr0 &= ~z80sio_channel::RR0_INTERRUPT_PENDING;
- LOGINT(" - Found an INT request, ");
- LOGINT("returning RR2: %02x\n", m_chanB->m_rr2 );
+ m_int_state[prio[i]] |= Z80_DAISY_IEO; // Set IUS bit (called IEO in z80 daisy lingo)
+ unsigned const vector = read_vector();
+ LOGINT(" - Found an INT request, returning RR2: %02x\n", vector);
check_interrupts();
- ret = m_chanB->m_rr2;
- break;
+ return vector;
}
}
+
// Did we not find a vector? Get the notion of a default vector from the CPU implementation
- if (ret == -1 && m_cputag != nullptr)
+ logerror(" - failed to find an interrupt to ack!\n");
+ if (m_cputag)
+ {
+ // default irq vector is -1 for 68000 but 0 for z80 for example...
+ // FIXME: use an optional_device or something
+ int const ret = owner()->subdevice<cpu_device>(m_cputag)->default_irq_vector();
+ LOGINT(" - failed to find an interrupt to ack [%s], returning default IRQ vector: %02x\n", m_cputag, ret);
+ return ret;
+ }
+
+ // indicate default vector
+ return -1;
+}
+
+int i8274_new_device::z80daisy_irq_ack()
+{
+ // FIXME: we're not modelling the full behaviour of this chip
+ // The 8274 is designed to work with Intel processors with multiple interrupt acknowledge cycles
+ // Values placed on the bus depend on WR2 A mode bits and /IPI input
+ // +----+----+----+------+--------------+-------+--------+
+ // | D5 | D4 | D3 | /IPI | Mode | Cycle | Data |
+ // +----+----+----+------+--------------+-------+--------+
+ // | 0 | - | - | - | non-vectored | - | hi-Z |
+ // +----+----+----+------+--------------+-------+--------+
+ // | 1 | 0 | 0 | 0 | 8085 (1) | 1 | 0xcd |
+ // | | | | | | 2 | vector |
+ // | | | | | | 3 | 0x00 |
+ // +----+----+----+------+--------------+-------+--------+
+ // | 1 | 0 | 0 | 1 | 8085 (1) | 1 | 0xcd |
+ // | | | | | | 2 | hi-Z |
+ // | | | | | | 3 | hi-Z |
+ // +----+----+----+------+--------------+-------+--------+
+ // | 1 | 0 | 1 | 0 | 8085 (2) | 1 | hi-Z |
+ // | | | | | | 2 | vector |
+ // | | | | | | 3 | 0x00 |
+ // +----+----+----+------+--------------+-------+--------+
+ // | 1 | 0 | 1 | 1 | 8085 (2) | 1 | hi-Z |
+ // | | | | | | 2 | hi-Z |
+ // | | | | | | 3 | hi-Z |
+ // +----+----+----+------+--------------+-------+--------+
+ // | 1 | 1 | 0 | 0 | 8086 | 1 | hi-Z |
+ // | | | | | | 2 | vector |
+ // +----+----+----+------+--------------+-------+--------+
+ // | 1 | 1 | 0 | 1 | 8086 | 1 | hi-Z |
+ // | | | | | | 2 | hi-Z |
+ // +----+----+----+------+--------------+-------+--------+
+ LOGINT("%s \n", FUNCNAME);
+
+ // don't do this in non-vectored mode
+ if (m_chanB->m_wr2 & WR2_VECTORED_INT)
+ {
+ // loop over all interrupt sources
+ int const *const prio = interrupt_priorities();
+ for (int i = 0; ARRAY_LENGTH(m_int_state) > i; ++i)
+ {
+ // find the first channel with an interrupt requested
+ if (m_int_state[prio[i]] & Z80_DAISY_INT)
+ {
+ m_int_state[prio[i]] |= Z80_DAISY_IEO; // Set IUS bit (called IEO in z80 daisy lingo)
+ unsigned const vector = read_vector();
+ LOGINT(" - Found an INT request, returning RR2: %02x\n", vector);
+ check_interrupts();
+ return vector;
+ }
+ }
+
+ // Did we not find a vector? Get the notion of a default vector from the CPU implementation
+ logerror(" - failed to find an interrupt to ack!\n");
+ }
+
+ if (m_cputag)
{
// default irq vector is -1 for 68000 but 0 for z80 for example...
- ret = owner()->subdevice<cpu_device>(m_cputag)->default_irq_vector();
- LOGINT(" - failed to find an interrupt to ack [%s], returning default IRQ vector: %02x\n", m_cputag, ret );
- logerror("z80sio_irq_ack: failed to find an interrupt to ack!\n");
+ // FIXME: use an optional_device or something
+ int const ret = owner()->subdevice<cpu_device>(m_cputag)->default_irq_vector();
+ LOGINT(" - failed to find an interrupt to ack [%s], returning default IRQ vector: %02x\n", m_cputag, ret);
+ return ret;
}
- return ret;
+ // indicate default vector
+ return -1;
}
@@ -278,27 +552,13 @@ int z80sio_device::z80daisy_irq_ack()
//-------------------------------------------------
void z80sio_device::z80daisy_irq_reti()
{
- LOGINT("%s %s \n",tag(), FUNCNAME);
-
- if((m_variant == TYPE_I8274) || (m_variant == TYPE_UPD7201))
- {
- LOGINT(" - I8274 and UPD7201 lacks RETI detection, no action taken\n");
- return;
- }
+ LOGINT("%s\n", FUNCNAME);
+ return_from_interrupt();
+}
- // loop over all interrupt sources
- for (auto & elem : m_int_state)
- {
- // find the first channel with an interrupt requested
- if (elem & Z80_DAISY_IEO)
- {
- // clear the IEO state and update the IRQs
- elem &= ~Z80_DAISY_IEO;
- check_interrupts();
- return;
- }
- }
- LOGINT("z80sio_irq_reti: failed to find an interrupt to clear IEO on!\n");
+void i8274_new_device::z80daisy_irq_reti()
+{
+ LOGINT("%s - i8274/uPD7201 lacks RETI detection, no action taken\n", FUNCNAME);
}
@@ -307,7 +567,7 @@ void z80sio_device::z80daisy_irq_reti()
//-------------------------------------------------
void z80sio_device::check_interrupts()
{
- LOGINT("%s %s \n",FUNCNAME, tag());
+ LOGINT("%s %s \n", FUNCNAME, tag());
int state = (z80daisy_irq_state() & Z80_DAISY_INT) ? ASSERT_LINE : CLEAR_LINE;
m_out_int_cb(state);
}
@@ -328,44 +588,114 @@ void z80sio_device::reset_interrupts()
check_interrupts();
}
-int z80sio_device::get_interrupt_prio(int index, int type)
+//-------------------------------------------------
+// trigger_interrupt - interrupt has fired
+//-------------------------------------------------
+void z80sio_device::trigger_interrupt(int index, int type)
+{
+ LOGINT("%s Chan:%c Type:%s\n", FUNCNAME, 'A' + index, std::array<char const *, 3>
+ {{"INT_TRANSMIT", "INT_EXTERNAL", "INT_RECEIVE"}}[type]);
+
+ // trigger interrupt
+ m_int_state[(index * 3) + type] |= Z80_DAISY_INT;
+ m_chanA->m_rr0 |= RR0_INTERRUPT_PENDING;
+
+ // check for interrupt
+ check_interrupts();
+}
+
+
+//-------------------------------------------------
+// clear_interrupt - interrupt has been cleared
+//-------------------------------------------------
+void z80sio_device::clear_interrupt(int index, int type)
{
- int prio_level = -1;
- int priority = -1;
+ LOGINT("%s Chan:%c Type:%s\n", FUNCNAME, 'A' + index, std::array<char const *, 3>
+ {{"INT_TRANSMIT", "INT_EXTERNAL", "INT_RECEIVE"}}[type]);
+
+ // clear interrupt
+ m_int_state[(index * 3) + type] &= ~Z80_DAISY_INT;
+ if (std::find_if(std::begin(m_int_state), std::end(m_int_state), [] (int state) { return bool(state & Z80_DAISY_INT); }) == std::end(m_int_state))
+ m_chanA->m_rr0 &= ~RR0_INTERRUPT_PENDING;
- if ((m_variant == TYPE_I8274) || (m_variant == TYPE_UPD7201))
+ // update interrupt output
+ check_interrupts();
+}
+
+
+//-------------------------------------------------
+// return_from_interrupt - reset interrupt under
+// service latch
+//-------------------------------------------------
+void z80sio_device::return_from_interrupt()
+{
+ // loop over all interrupt sources
+ int const *const prio = interrupt_priorities();
+ for (int i = 0; ARRAY_LENGTH(m_int_state) > i; ++i)
{
- /* These CPU variants use Bit 2 of WR2 of Channnel A to determine the priority Hi to Lo:
- 0: RxA TxA RxB TxB ExtA ExtB
- 1: RxA RxB TxA TxB ExtA ExtB */
- switch(type)
+ // find the first channel with an interrupt requested
+ if (m_int_state[prio[i]] & (Z80_DAISY_IEO))
{
- case z80sio_channel::INT_RECEIVE:
- case z80sio_channel::INT_SPECIAL: prio_level = z80sio_channel::INT_RCV_SPC_PRI_LVL; break; // 0
- case z80sio_channel::INT_TRANSMIT: prio_level = z80sio_channel::INT_TRANSMIT_PRI_LVL; break; // 1
- case z80sio_channel::INT_EXTERNAL: prio_level = z80sio_channel::INT_EXTERNAL_PRI_LVL; break; // 2
- default:
- logerror("Bad interrupt source being prioritized!");
- return -1;
+ // clear the IEO state and update the IRQs
+ m_int_state[prio[i]] &= ~Z80_DAISY_IEO;
+ check_interrupts();
+ LOGINT("%s - cleared IEO\n", FUNCNAME);
+ return;
}
- // Assume that the PRIORITY bit is set
- priority = (prio_level * 2) + index;
+ }
+ LOGINT("%s - failed to find an interrupt to clear IEO on!", FUNCNAME);
+}
+
+
+//-------------------------------------------------
+// read_vector - read modified interrupt vector
+//-------------------------------------------------
+uint8_t z80sio_device::read_vector()
+{
+ uint8_t vec = m_chanB->m_wr2;
- // Check if it actually was cleared
- if ( (m_chanA->m_wr2 & z80sio_channel::WR2_PRIORITY) == 0)
+ // if status doesn't affect vector, return unmodified value
+ if (!(m_chanB->m_wr1 & WR1_STATUS_VECTOR))
+ return vec;
+
+ // modify vector for highest-priority pending interrupt
+ int const *const prio = interrupt_priorities();
+ vec &= 0xf1U;
+ for (int i = 0; ARRAY_LENGTH(m_int_state) > i; ++i)
+ {
+ if (m_int_state[prio[i]] & Z80_DAISY_INT)
{
- // Adjust priority if needed, only affects TxA and RxB
- if (index == CHANNEL_A && type == z80sio_channel::INT_TRANSMIT )
- priority--;
- else if (index == CHANNEL_B && type == z80sio_channel::INT_RECEIVE )
- priority++;
+ constexpr uint8_t RR1_SPECIAL(RR1_RX_OVERRUN_ERROR | RR1_CRC_FRAMING_ERROR | RR1_END_OF_FRAME);
+ switch (prio[i])
+ {
+ case 0 + z80sio_channel::INT_TRANSMIT:
+ return vec | 0x08U;
+ case 0 + z80sio_channel::INT_EXTERNAL:
+ return vec | 0x0aU;
+ case 0 + z80sio_channel::INT_RECEIVE:
+ if (((m_chanA->m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_ALL_PARITY) && (m_chanA->m_rr1 & (RR1_SPECIAL | RR1_PARITY_ERROR)))
+ return vec | 0x0eU;
+ else if (((m_chanA->m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_ALL) && (m_chanA->m_rr1 & RR1_SPECIAL))
+ return vec | 0x0eU;
+ else
+ return vec | 0x0cU;
+ case 3 + z80sio_channel::INT_TRANSMIT:
+ return vec | 0x00U;
+ case 3 + z80sio_channel::INT_EXTERNAL:
+ return vec | 0x02U;
+ case 3 + z80sio_channel::INT_RECEIVE:
+ if (((m_chanB->m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_ALL_PARITY) && (m_chanB->m_rr1 & (RR1_SPECIAL | RR1_PARITY_ERROR)))
+ return vec | 0x06U;
+ else if (((m_chanB->m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_ALL) && (m_chanB->m_rr1 & RR1_SPECIAL))
+ return vec | 0x06U;
+ else
+ return vec | 0x04U;
+ }
}
}
- else // Plain old z80sio
- {
- priority = (index << 2) | type;
- }
- return priority;
+
+ // no interrupt pending - stuff 011 in the variable bits
+ return vec | 0x06U;
}
/*
@@ -375,59 +705,92 @@ int z80sio_device::get_interrupt_prio(int index, int type)
interrrupt is generated, otherwise it will indicate the previous state."
8274: "If RR2 is specified but not read, no internal interrupts, regardless of priority, are accepted."
*/
-uint8_t z80sio_device::modify_vector(int index, int type)
+uint8_t i8274_new_device::read_vector()
{
- uint8_t vector = m_chanB->m_wr2;
- if((m_variant == TYPE_I8274) || (m_variant == TYPE_UPD7201))
+ // 8086 and 8085 modes have different variable bits
+ bool const aff(m_chanB->m_wr1 & WR1_STATUS_VECTOR);
+ int const shift(((m_chanA->m_wr2 & WR2_MODE_MASK) == WR2_MODE_8086_8088) ? 0 : 2);
+ uint8_t vec(m_chanB->m_wr2);
+
+ // if status doesn't affect vector, return unmodified value
+ if (aff)
+ vec &= ~(0x07U << shift);
+
+ // modify vector for highest-priority pending interrupt
+ int const *const prio = interrupt_priorities();
+ for (int i = 0; ARRAY_LENGTH(m_int_state) > i; ++i)
{
- if (m_chanB->m_wr1 & z80sio_channel::WR1_STATUS_VECTOR)
+ if (m_int_state[prio[i]] & Z80_DAISY_INT)
{
- vector = (!index << 2) | type;
- if((m_chanA->m_wr1 & 0x18) == z80sio_channel::WR2_MODE_8086_8088)
+ constexpr uint8_t RR1_SPECIAL(RR1_RX_OVERRUN_ERROR | RR1_CRC_FRAMING_ERROR | RR1_END_OF_FRAME);
+
+ // in non-vectored mode this serves the same function as the end of the second acknowldege cycle
+ if (!(m_chanB->m_wr2 & WR2_VECTORED_INT) && !machine().side_effect_disabled())
{
- vector = (m_chanB->m_wr2 & 0xf8) | vector; // m_chanB->m_wr2;
+ m_int_state[prio[i]] |= Z80_DAISY_IEO;
+ check_interrupts();
}
- else
+
+ // if status doesn't affect vector return unmodified value
+ if (!aff)
+ return vec;
+
+ switch (prio[i])
{
- vector = (m_chanB->m_wr2 & 0xe3) | (vector << 2); //(m_chanB->m_wr2 << 2);
+ case 0 + z80sio_channel::INT_TRANSMIT:
+ return vec | (0x04U << shift);
+ case 0 + z80sio_channel::INT_EXTERNAL:
+ return vec | (0x05U << shift);
+ case 0 + z80sio_channel::INT_RECEIVE:
+ if (((m_chanA->m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_ALL_PARITY) && (m_chanA->m_rr1 & (RR1_SPECIAL | RR1_PARITY_ERROR)))
+ return vec | (0x07U << shift);
+ else if (((m_chanA->m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_ALL) && (m_chanA->m_rr1 & RR1_SPECIAL))
+ return vec | (0x07U << shift);
+ else
+ return vec | (0x06U << shift);
+ case 3 + z80sio_channel::INT_TRANSMIT:
+ return vec | (0x00U << shift);
+ case 3 + z80sio_channel::INT_EXTERNAL:
+ return vec | (0x01U << shift);
+ case 3 + z80sio_channel::INT_RECEIVE:
+ if (((m_chanB->m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_ALL_PARITY) && (m_chanB->m_rr1 & (RR1_SPECIAL | RR1_PARITY_ERROR)))
+ return vec | (0x03U << shift);
+ else if (((m_chanB->m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_ALL) && (m_chanB->m_rr1 & RR1_SPECIAL))
+ return vec | (0x03U << shift);
+ else
+ return vec | (0x02U << shift);
}
}
}
- else
- {
- if (m_chanB->m_wr1 & z80sio_channel::WR1_STATUS_VECTOR)
- {
- // status affects vector
- vector = (m_chanB->m_wr2 & 0xf1) | (!index << 3) | (type << 1);
- }
- }
- return vector;
+
+ // no interrupt pending - stuff 111 in the variable bits
+ return aff ? (vec | (0x07 << shift)) : vec;
}
+
//-------------------------------------------------
-// trigger_interrupt - TODO: needs attention for SIO
+// interrupt_priorities - get interrupt indexes
+// in priority order
//-------------------------------------------------
-void z80sio_device::trigger_interrupt(int index, int type)
+int const *z80sio_device::interrupt_priorities() const
{
- uint8_t priority = get_interrupt_prio(index, type);
- uint8_t vector = modify_vector(index, type);
-
- LOGINT("%s %s Chan:%c Type:%s\n", tag(), FUNCNAME, 'A' + index, std::array<char const *, 4>
- {{"INT_TRANSMIT", "INT_EXTERNAL", "INT_RECEIVE", "INT_SPECIAL"}}[type]);
- LOGINT(" - Priority:%02x Vector:%02x\n", priority, vector);
-
- // update vector register
- m_chanB->m_rr2 = vector;
-
- // trigger interrupt
- m_int_state[priority] |= Z80_DAISY_INT;
- m_chanA->m_rr0 |= z80sio_channel::RR0_INTERRUPT_PENDING;
-
- // remember the source and channel
- m_int_source[priority] = (type & 0xff) | (index << 8);
+ static constexpr EQUIVALENT_ARRAY(m_int_state, int) prio{
+ 0 + z80sio_channel::INT_RECEIVE, 0 + z80sio_channel::INT_TRANSMIT, 0 + z80sio_channel::INT_EXTERNAL,
+ 3 + z80sio_channel::INT_RECEIVE, 3 + z80sio_channel::INT_TRANSMIT, 3 + z80sio_channel::INT_EXTERNAL };
+ return prio;
+}
- // check for interrupt
- check_interrupts();
+int const *i8274_new_device::interrupt_priorities() const
+{
+ static constexpr EQUIVALENT_ARRAY(m_int_state, int) prio_a{
+ 0 + z80sio_channel::INT_RECEIVE, 3 + z80sio_channel::INT_RECEIVE,
+ 0 + z80sio_channel::INT_TRANSMIT, 3 + z80sio_channel::INT_TRANSMIT,
+ 0 + z80sio_channel::INT_EXTERNAL, 3 + z80sio_channel::INT_EXTERNAL };
+ static constexpr EQUIVALENT_ARRAY(m_int_state, int) prio_b{
+ 0 + z80sio_channel::INT_RECEIVE, 0 + z80sio_channel::INT_TRANSMIT,
+ 3 + z80sio_channel::INT_RECEIVE, 3 + z80sio_channel::INT_TRANSMIT,
+ 0 + z80sio_channel::INT_EXTERNAL, 3 + z80sio_channel::INT_EXTERNAL };
+ return (m_chanA->m_wr2 & WR2_PRIORITY) ? prio_a : prio_b;
}
@@ -437,10 +800,13 @@ void z80sio_device::trigger_interrupt(int index, int type)
int z80sio_device::m1_r()
{
LOGINT("%s %s \n",FUNCNAME, tag());
- if((m_variant == TYPE_I8274) || (m_variant == TYPE_UPD7201))
- return 0;
- else
- return z80daisy_irq_ack();
+ return z80daisy_irq_ack();
+}
+
+int i8274_new_device::m1_r()
+{
+ LOGINT("%s %s \n",FUNCNAME, tag());
+ return 0;
}
@@ -508,9 +874,14 @@ WRITE8_MEMBER( z80sio_device::ba_cd_w )
//-------------------------------------------------
// z80sio_channel - constructor
//-------------------------------------------------
-z80sio_channel::z80sio_channel(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
- : device_t(mconfig, Z80SIO_CHANNEL, tag, owner, clock)
- , device_serial_interface(mconfig, *this)
+z80sio_channel::z80sio_channel(
+ const machine_config &mconfig,
+ device_type type,
+ const char *tag,
+ device_t *owner,
+ uint32_t clock,
+ uint8_t rr1_auto_reset)
+ : device_t(mconfig, type, tag, owner, clock)
, m_rx_fifo_depth(0)
, m_rx_data_fifo(0)
, m_rx_error_fifo(0)
@@ -521,23 +892,30 @@ z80sio_channel::z80sio_channel(const machine_config &mconfig, const char *tag, d
, m_rx_first(0)
, m_rx_break(0)
, m_rx_rr0_latch(0)
- , m_rxd(0)
- , m_sh(0)
- , m_cts(0)
- , m_dcd(0)
+ , m_rxd(1)
, m_tx_data(0)
- , m_tx_clock(0)
- , m_dtr(0)
- , m_rts(0)
- , m_sync(0)
+ , m_tx_clock(0), m_tx_count(0), m_tx_bits(0), m_tx_parity(0), m_tx_framing(0), m_tx_special(0), m_tx_sr(0), m_tx_crc(0), m_tx_hist(0)
+ , m_txd(1), m_dtr(0), m_rts(0)
+ , m_ext_latched(0), m_cts(0), m_dcd(0), m_sync(0)
+ , m_rr1_auto_reset(rr1_auto_reset)
{
LOG("%s\n",FUNCNAME);
// Reset all registers
- m_rr0 = m_rr1 = m_rr2 = 0;
+ m_rr0 = m_rr1 = 0;
m_wr0 = m_wr1 = m_wr2 = m_wr3 = m_wr4 = m_wr5 = m_wr6 = m_wr7 = 0;
}
+z80sio_channel::z80sio_channel(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : z80sio_channel(mconfig, Z80SIO_CHANNEL, tag, owner, clock, RR1_CRC_FRAMING_ERROR)
+{
+}
+
+i8274_channel::i8274_channel(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
+ : z80sio_channel(mconfig, I8274_CHANNEL, tag, owner, clock, RR1_RX_OVERRUN_ERROR)
+{
+}
+
//-------------------------------------------------
// resove_objects - channel setup
@@ -547,7 +925,6 @@ void z80sio_channel::device_resolve_objects()
LOG("%s\n",FUNCNAME);
m_uart = downcast<z80sio_device *>(owner());
m_index = m_uart->get_channel_index(this);
- m_variant = m_uart->m_variant;
}
//-------------------------------------------------
@@ -560,7 +937,6 @@ void z80sio_channel::device_start()
// state saving
save_item(NAME(m_rr0));
save_item(NAME(m_rr1));
- save_item(NAME(m_rr2));
save_item(NAME(m_wr0));
save_item(NAME(m_wr1));
save_item(NAME(m_wr2));
@@ -579,15 +955,23 @@ void z80sio_channel::device_start()
save_item(NAME(m_rx_first));
save_item(NAME(m_rx_break));
save_item(NAME(m_rx_rr0_latch));
- save_item(NAME(m_sh));
- save_item(NAME(m_cts));
- save_item(NAME(m_dcd));
save_item(NAME(m_tx_data));
save_item(NAME(m_tx_clock));
+ save_item(NAME(m_tx_count));
+ save_item(NAME(m_tx_bits));
+ save_item(NAME(m_tx_parity));
+ save_item(NAME(m_tx_framing));
+ save_item(NAME(m_tx_special));
+ save_item(NAME(m_tx_sr));
+ save_item(NAME(m_tx_crc));
+ save_item(NAME(m_tx_hist));
+ save_item(NAME(m_txd));
save_item(NAME(m_dtr));
save_item(NAME(m_rts));
+ save_item(NAME(m_ext_latched));
+ save_item(NAME(m_dcd));
save_item(NAME(m_sync));
- save_item(NAME(m_variant));
+ save_item(NAME(m_cts));
}
@@ -602,120 +986,213 @@ void z80sio_channel::device_reset()
m_rx_fifo_depth = 0;
m_rx_data_fifo = m_rx_error_fifo = 0U;
receive_reset();
- transmit_register_reset();
+ m_tx_count = 0;
+ m_tx_bits = 0;
// disable receiver
m_wr3 &= ~WR3_RX_ENABLE;
// disable transmitter
m_wr5 &= ~WR5_TX_ENABLE;
- m_rr0 |= RR0_TX_BUFFER_EMPTY;
+ m_rr0 |= RR0_TX_BUFFER_EMPTY | RR0_TX_UNDERRUN;
m_rr1 |= RR1_ALL_SENT;
// reset external lines
- set_rts(1);
- set_dtr(1);
+ out_rts_cb(m_rts = 1);
+ out_dtr_cb(m_dtr = 1);
// reset interrupts
if (m_index == z80sio_device::CHANNEL_A)
- {
m_uart->reset_interrupts();
- }
}
+
//-------------------------------------------------
-// tra_callback -
+// transmit_enable - start transmission if
+// conditions met
//-------------------------------------------------
-void z80sio_channel::tra_callback()
+void z80sio_channel::transmit_enable()
{
- if (!(m_wr5 & WR5_TX_ENABLE))
+ if (!m_tx_bits && transmit_allowed())
{
- LOGBIT("%s() \"%s \"Channel %c transmit mark 1 m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_wr5);
- // transmit mark
- if (m_index == z80sio_device::CHANNEL_A)
- m_uart->m_out_txda_cb(1);
- else
- m_uart->m_out_txdb_cb(1);
- }
- else if (m_wr5 & WR5_SEND_BREAK)
- {
- LOGBIT("%s() \"%s \"Channel %c send break 1 m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_wr5);
- // transmit break
- if (m_index == z80sio_device::CHANNEL_A)
- m_uart->m_out_txda_cb(0);
- else
- m_uart->m_out_txdb_cb(0);
+ if ((m_wr4 & WR4_STOP_BITS_MASK) == WR4_STOP_BITS_SYNC)
+ {
+ switch (m_wr4 & WR4_SYNC_MODE_MASK)
+ {
+ // SDLC/HDLC - enabling transmit loads a flag byte
+ case WR4_SYNC_MODE_SDLC:
+ LOGTX("Channel %c SDLC transmit enabled - load initial flag\n", 'A' + m_index);
+ tx_setup_flag();
+ m_tx_special = 0;
+ break;
+ default:
+ logerror("unsupported synchronous mode transmit\n");
+ }
+ }
+ else if (!(m_rr0 & RR0_TX_BUFFER_EMPTY))
+ {
+ async_tx_setup();
+ }
}
- else if (!is_transmit_register_empty())
- {
- int db = transmit_register_get_data_bit();
+}
- LOGBIT("%s() \"%s \"Channel %c transmit data bit %d m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, db, m_wr5);
- // transmit data
- if (m_index == z80sio_device::CHANNEL_A)
- m_uart->m_out_txda_cb(db);
- else
- m_uart->m_out_txdb_cb(db);
- }
- else
+//-------------------------------------------------
+// transmit_complete - transmit shift register
+// empty
+//-------------------------------------------------
+void z80sio_channel::transmit_complete()
+{
+ LOG("%s %s\n",FUNCNAME, tag());
+
+ if (transmit_allowed())
{
- LOGBIT("%s() \"%s \"Channel %c Failed to transmit m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_wr5);
- logerror("%s \"%s \"Channel %c Failed to transmit\n", FUNCNAME, owner()->tag(), 'A' + m_index);
+ // transmit enabled, let's go
+ if ((m_wr4 & WR4_STOP_BITS_MASK) == WR4_STOP_BITS_SYNC)
+ {
+ switch (m_wr4 & WR4_SYNC_MODE_MASK)
+ {
+ // SDLC/HDLC - enabling transmit loads a flag character
+ case WR4_SYNC_MODE_SDLC:
+ sdlc_tx_sr_empty();
+ break;
+ default:
+ logerror("unsupported synchronous mode transmit\n");
+ }
+ }
+ else if (!(m_rr0 & RR0_TX_BUFFER_EMPTY))
+ {
+ // async mode, with data available
+ async_tx_setup();
+ }
+ else
+ {
+ LOGTX("%s() \"%s \"Channel %c Transmit buffer empty m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_wr5);
+ }
}
}
-
//-------------------------------------------------
-// tra_complete -
+// sdlc_tx_sr_empty - set up next chunk of bits
+// to send
//-------------------------------------------------
-void z80sio_channel::tra_complete()
+void z80sio_channel::sdlc_tx_sr_empty()
{
- LOG("%s %s\n",FUNCNAME, tag());
- if ((m_wr5 & WR5_TX_ENABLE) && !(m_wr5 & WR5_SEND_BREAK) && !(m_rr0 & RR0_TX_BUFFER_EMPTY))
+ if (!(m_rr0 & RR0_TX_BUFFER_EMPTY))
{
- LOGTX("%s() \"%s \"Channel %c Transmit Data Byte '%02x' m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_tx_data, m_wr5);
-
- transmit_register_setup(m_tx_data);
+ LOGTX("%s() Channel %c Transmit Data Byte '%02x' m_wr5:%02x\n", FUNCNAME, 'A' + m_index, m_tx_data, m_wr5);
+ tx_setup(m_tx_data, get_tx_word_length(m_tx_data), (m_wr4 & WR4_PARITY_ENABLE) ? 1 : 0, 0);
// empty transmit buffer
+ m_tx_special = 0;
m_rr0 |= RR0_TX_BUFFER_EMPTY;
- if ((m_wr1 & WR1_WRDY_ENABLE) && !(m_wr1 & WR1_WRDY_ON_RX_TX))
- set_ready(true);
if (m_wr1 & WR1_TX_INT_ENABLE)
m_uart->trigger_interrupt(m_index, INT_TRANSMIT);
}
- else if (m_wr5 & WR5_SEND_BREAK)
+ else if (m_rr0 & RR0_TX_UNDERRUN)
{
- LOGTX("%s() \"%s \"Channel %c Transmit Break 0 m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_wr5);
- // transmit break
- if (m_index == z80sio_device::CHANNEL_A)
- m_uart->m_out_txda_cb(0);
- else
- m_uart->m_out_txdb_cb(0);
+ LOGTX("%s() Channel %c Underrun - load flag m_wr5:%02x\n", FUNCNAME, 'A' + m_index, m_wr5);
+ tx_setup_flag();
+
+ // if the CRC was just sent, generate an interrupt indicating that the next frame can be sent
+ if (m_tx_special)
+ {
+ m_tx_special = 0;
+ if (m_wr1 & WR1_TX_INT_ENABLE)
+ m_uart->trigger_interrupt(m_index, INT_TRANSMIT);
+ }
+ m_rr1 |= RR1_ALL_SENT;
}
else
{
- LOGTX("%s() \"%s \"Channel %c Transmit Mark 1 m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_wr5);
- // transmit mark
- if (m_index == z80sio_device::CHANNEL_A)
- m_uart->m_out_txda_cb(1);
- else
- m_uart->m_out_txdb_cb(1);
+ LOGTX("%s() Channel %c Transmit FCS '%04x' m_wr5:%02x\n", FUNCNAME, 'A' + m_index, m_tx_crc, m_wr5);
+
+ // just for fun, SDLC sends the FCS inverted in reverse bit order
+ tx_setup(~BITSWAP16(m_tx_crc, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15), 16, 0, 0);
+
+ // set the underrun flag so it will send a flag next time
+ m_rr0 |= RR0_TX_UNDERRUN;
+ m_tx_special = 1;
+ trigger_ext_int();
}
+}
- // if transmit buffer is empty
- if (m_rr0 & RR0_TX_BUFFER_EMPTY)
- {
- LOGTX("%s() \"%s \"Channel %c Transmit buffer empty m_wr5:%02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_wr5);
- // then all characters have been sent
- m_rr1 |= RR1_ALL_SENT;
- // when the RTS bit is reset, the _RTS output goes high after the transmitter empties
- if (!m_rts)
- set_rts(1);
+//-------------------------------------------------
+// async_tx_setup - set up for asynchronous
+// transmission
+//-------------------------------------------------
+void z80sio_channel::async_tx_setup()
+{
+ LOGTX("%s() Channel %c Transmit Data Byte '%02x' m_wr5:%02x\n", FUNCNAME, 'A' + m_index, m_tx_data, m_wr5);
+
+ tx_setup(uint16_t(m_tx_data) << 1, get_tx_word_length(m_tx_data) + 1, (m_wr4 & WR4_PARITY_ENABLE) ? 2 : 0, 0);
+ ++m_tx_bits; // stop bit
+
+ // empty transmit buffer
+ m_rr0 |= RR0_TX_BUFFER_EMPTY;
+ if (m_wr1 & WR1_TX_INT_ENABLE)
+ m_uart->trigger_interrupt(m_index, INT_TRANSMIT);
+}
+
+
+//-------------------------------------------------
+// reset_ext_status - reset external/status
+// condiotions
+//-------------------------------------------------
+void z80sio_channel::reset_ext_status()
+{
+ // this will clear latched external pin state
+ m_ext_latched = 0;
+ read_ext();
+
+ // Clear any pending External interrupt
+ m_uart->clear_interrupt(m_index, INT_EXTERNAL);
+}
+
+
+//-------------------------------------------------
+// read_ext - copy external status to register
+//-------------------------------------------------
+void z80sio_channel::read_ext()
+{
+ // clear to send
+ if (m_cts)
+ m_rr0 &= ~RR0_CTS;
+ else
+ m_rr0 |= RR0_CTS;
+
+ // data carrier detect
+ if (m_dcd)
+ m_rr0 &= ~RR0_DCD;
+ else
+ m_rr0 |= RR0_DCD;
+
+ // sync is a general-purpose input in asynchronous mode
+ if ((m_wr4 & WR4_STOP_BITS_MASK) != WR4_STOP_BITS_SYNC)
+ {
+ if (m_sync)
+ m_rr0 &= ~RR0_SYNC_HUNT;
+ else
+ m_rr0 |= RR0_SYNC_HUNT;
}
}
+//-------------------------------------------------
+// trigger_ext_int - trigger external signal
+// interrupt
+//-------------------------------------------------
+void z80sio_channel::trigger_ext_int()
+{
+ // update line
+ if (!m_ext_latched)
+ read_ext();
+ m_ext_latched = 1;
+
+ // trigger interrupt if enabled
+ if (m_wr1 & WR1_EXT_INT_ENABLE)
+ m_uart->trigger_interrupt(m_index, INT_EXTERNAL);
+}
+
//-------------------------------------------------
// get_clock_mode - get clock divisor
@@ -746,63 +1223,24 @@ int z80sio_channel::get_clock_mode()
CR5 = m_wr5 and SR1 = m_rr1
*/
-void z80sio_channel::set_rts(int state)
-{
- LOG("%s(%d) \"%s\" Channel %c \n", FUNCNAME, state, owner()->tag(), 'A' + m_index);
- if (m_index == z80sio_device::CHANNEL_A)
- m_uart->m_out_rtsa_cb(state);
- else
- m_uart->m_out_rtsb_cb(state);
-}
-
-void z80sio_channel::update_rts()
+void z80sio_channel::update_dtr_rts_break()
{
// LOG("%s(%d) \"%s\" Channel %c \n", FUNCNAME, state, owner()->tag(), 'A' + m_index);
LOG("%s() \"%s\" Channel %c \n", FUNCNAME, owner()->tag(), 'A' + m_index);
- if (m_wr5 & WR5_RTS)
- {
- // when the RTS bit is set, the _RTS output goes low
- set_rts(0);
- m_rts = 1;
- }
- else
- {
- // when the RTS bit is reset, the _RTS output goes high after the transmitter empties
- m_rts = 0;
- }
-
- // data terminal ready output follows the state programmed into the DTR bit*/
- set_dtr((m_wr5 & WR5_DTR) ? 0 : 1);
-}
-
-void z80sio_channel::set_ready(bool ready)
-{
- // WAIT mode not supported yet
- if (!(m_wr1 & WR1_WRDY_FUNCTION))
- return;
-
- logerror("Channel %sready\n", ready ? "" : "not ");
- if (m_index == z80sio_device::CHANNEL_A)
- m_uart->m_out_wrdya_cb(ready ? 0 : 1);
+ // RTS is affected by transmit queue state in asynchronous mode
+ if (m_wr5 & WR5_RTS)
+ set_rts(0); // when the RTS bit is set, the _RTS output goes low
+ else if ((m_wr4 & WR4_STOP_BITS_MASK) == WR4_STOP_BITS_SYNC)
+ set_rts(1); // in synchronous mode, there's no automatic RTS
else
- m_uart->m_out_wrdyb_cb(ready ? 0 : 1);
-}
+ set_rts(((m_rr0 & RR0_TX_BUFFER_EMPTY) && !m_tx_count) ? 1 : 0); // TODO: is this affected by transmit enable?
-//-------------------------------------------------
-// get_stop_bits - get number of stop bits
-//-------------------------------------------------
-device_serial_interface::stop_bits_t z80sio_channel::get_stop_bits()
-{
- LOG("%s %s\n",FUNCNAME, tag());
- switch (m_wr4 & WR4_STOP_BITS_MASK)
- {
- case WR4_STOP_BITS_1: return STOP_BITS_1;
- case WR4_STOP_BITS_1_5: return STOP_BITS_1_5;
- case WR4_STOP_BITS_2: return STOP_BITS_2;
- }
+ // break immediately forces spacing condition on TxD output
+ out_txd_cb((m_wr5 & WR5_SEND_BREAK) ? 0 : m_txd);
- return STOP_BITS_0;
+ // data terminal ready output follows the state programmed into the DTR bit
+ set_dtr((m_wr5 & WR5_DTR) ? 0 : 1);
}
@@ -829,19 +1267,32 @@ int z80sio_channel::get_rx_word_length()
//-------------------------------------------------
// get_tx_word_length - get transmit word length
//-------------------------------------------------
-int z80sio_channel::get_tx_word_length()
+int z80sio_channel::get_tx_word_length() const
{
- LOG("%s %s\n",FUNCNAME, tag());
- int bits = 5;
+ LOG("%s\n", FUNCNAME);
switch (m_wr5 & WR5_TX_WORD_LENGTH_MASK)
{
- case WR5_TX_WORD_LENGTH_5: bits = 5; break;
- case WR5_TX_WORD_LENGTH_6: bits = 6; break;
- case WR5_TX_WORD_LENGTH_7: bits = 7; break;
- case WR5_TX_WORD_LENGTH_8: bits = 8; break;
+ case WR5_TX_WORD_LENGTH_5: return 5;
+ case WR5_TX_WORD_LENGTH_6: return 6;
+ case WR5_TX_WORD_LENGTH_7: return 7;
+ case WR5_TX_WORD_LENGTH_8: return 8;
}
+ return 5;
+}
+
+int z80sio_channel::get_tx_word_length(uint8_t data) const
+{
+ LOG("%s(%02x)\n", FUNCNAME, data);
+
+ // deal with "five bits or less" mode (the actual chips probably detect a sentinel pattern in the transmit shift register)
+ int bits = get_tx_word_length();
+ if (5 == bits)
+ {
+ for (int b = 7; (b >= 4) && BIT(data, b); --b)
+ --bits;
+ }
return bits;
}
@@ -851,8 +1302,11 @@ int z80sio_channel::get_tx_word_length()
* Break/Abort latch. */
uint8_t z80sio_channel::do_sioreg_rr0()
{
- LOGR("%s %s\n",FUNCNAME, tag());
- return m_rr0;
+ LOGR("%s\n", FUNCNAME);
+ if (m_tx_special)
+ return m_rr0 & ~RR0_TX_BUFFER_EMPTY;
+ else
+ return m_rr0;
}
/*
@@ -860,10 +1314,7 @@ uint8_t z80sio_channel::do_sioreg_rr0()
* codes for the I-Field in the SDLC Receive Mode. */
uint8_t z80sio_channel::do_sioreg_rr1()
{
- LOGR("%s %s\n",FUNCNAME, tag());
- // channel B only, channel A returns 0
- if (m_index == z80sio_device::CHANNEL_A) return 0;
-
+ LOGR("%s\n", FUNCNAME);
return m_rr1;
}
@@ -890,41 +1341,12 @@ uint8_t z80sio_channel::do_sioreg_rr1()
uint8_t z80sio_channel::do_sioreg_rr2()
{
LOGINT("%s %s Chan:%c\n", tag(), FUNCNAME, 'A' + m_index);
- // channel B only, channel A returns 0
- if (m_index == z80sio_device::CHANNEL_A) return 0;
- LOGINT(" - Channel B so we might need to update the vector modification\n");
- // Assume the unmodified vector
- m_rr2 = m_uart->m_chanB->m_wr2;
-
- if((m_variant == z80sio_device::TYPE_I8274) || (m_variant == z80sio_device::TYPE_UPD7201))
- {
- int i = 0;
- LOGINT(" - 8274 or 7201 requires special care\n");
-
- // loop over all interrupt sources
- for (auto & elem : m_uart->m_int_state)
- {
- // find the first channel with an interrupt requested
- if (elem & Z80_DAISY_INT)
- {
- LOGINT(" - Checking an INT source %d\n", i);
- m_rr2 = m_uart->modify_vector((m_uart->m_int_source[i] >> 8) & 1, m_uart->m_int_source[i] & 3);
- LOGINT(" - Found an INT request to ack while reading RR2\n");
- elem = Z80_DAISY_IEO; // Set IUS bit (called IEO in z80 daisy lingo)
- m_uart->check_interrupts();
- break;
- }
- i++;
- }
- // If no pending interrupt were found set variable bits to ones.
- if (i >= 6)
- {
- m_rr2 |= 0x1F;
- m_uart->m_chanA->m_rr0 &= ~z80sio_channel::RR0_INTERRUPT_PENDING;
- }
- }
- return m_rr2;
+ // channel B only, channel A returns 0
+ if (m_index == z80sio_device::CHANNEL_A)
+ return 0U;
+ else
+ return m_uart->read_vector();
}
@@ -934,18 +1356,16 @@ uint8_t z80sio_channel::do_sioreg_rr2()
uint8_t z80sio_channel::control_read()
{
uint8_t data = 0;
- uint8_t reg = m_wr0 & WR0_REGISTER_MASK;
+ uint8_t const reg = m_wr0 & WR0_REGISTER_MASK;
//LOG("%s %s\n",FUNCNAME, tag());
- if (reg != 0)
- {
- // mask out register index
+ // mask out register index
+ if (!machine().side_effect_disabled())
m_wr0 &= ~WR0_REGISTER_MASK;
- }
switch (reg)
{
- case REG_RR0_STATUS: data = do_sioreg_rr0(); break;
+ case REG_RR0_STATUS: data = do_sioreg_rr0(); break;
case REG_RR1_SPEC_RCV_COND: data = do_sioreg_rr1(); break;
case REG_RR2_INTERRUPT_VECT: data = do_sioreg_rr2(); break;
default:
@@ -962,23 +1382,25 @@ uint8_t z80sio_channel::control_read()
Handle the WR0 CRC Reset/Init bits separatelly, needed by derived devices separatelly from the commands */
void z80sio_channel::do_sioreg_wr0_resets(uint8_t data)
{
- LOG("%s %s\n",FUNCNAME, tag());
+ LOG("%s\n", FUNCNAME);
switch (data & WR0_CRC_RESET_CODE_MASK)
{
case WR0_CRC_RESET_NULL:
- LOG("Z80SIO \"%s\" Channel %c : CRC_RESET_NULL\n", owner()->tag(), 'A' + m_index);
+ LOGCMD("Z80SIO Channel %c : CRC_RESET_NULL\n", 'A' + m_index);
break;
case WR0_CRC_RESET_RX: /* In Synchronous mode: all Os (zeros) (CCITT-O CRC-16) */
- LOG("Z80SIO \"%s\" Channel %c : CRC_RESET_RX - not implemented\n", owner()->tag(), 'A' + m_index);
+ LOGCMD("Z80SIO Channel %c : CRC_RESET_RX - not implemented\n", 'A' + m_index);
break;
case WR0_CRC_RESET_TX: /* In HDLC mode: all 1s (ones) (CCITT-1) */
- LOG("Z80SIO \"%s\" Channel %c : CRC_RESET_TX - not implemented\n", owner()->tag(), 'A' + m_index);
+ LOGCMD("Z80SIO Channel %c : CRC_RESET_TX\n", 'A' + m_index);
+ m_tx_crc = ((m_wr4 & WR4_SYNC_MODE_MASK) == WR4_SYNC_MODE_SDLC) ? ~uint16_t(0U) : uint16_t(0U);
break;
case WR0_CRC_RESET_TX_UNDERRUN: /* Resets Tx underrun/EOM bit (D6 of the SRO register) */
- LOG("Z80SIO \"%s\" Channel %c : CRC_RESET_TX_UNDERRUN - not implemented\n", owner()->tag(), 'A' + m_index);
+ LOGCMD("Z80SIO Channel %c : CRC_RESET_TX_UNDERRUN\n", 'A' + m_index);
+ m_rr0 &= ~RR0_TX_UNDERRUN;
break;
default: /* Will not happen unless someone messes with the mask */
- logerror("Z80SIO \"%s\" Channel %c : %s Wrong CRC reset/init command:%02x\n", owner()->tag(), 'A' + m_index, FUNCNAME, data & WR0_CRC_RESET_CODE_MASK);
+ logerror("Z80SIO Channel %c : %s Wrong CRC reset/init command:%02x\n", 'A' + m_index, FUNCNAME, data & WR0_CRC_RESET_CODE_MASK);
}
}
@@ -991,60 +1413,45 @@ void z80sio_channel::do_sioreg_wr0(uint8_t data)
switch (data & WR0_COMMAND_MASK)
{
case WR0_NULL:
- LOGCMD("%s %s Ch:%c : Null command\n", FUNCNAME, tag(), 'A' + m_index);
+ LOGCMD("%s Ch:%c : Null command\n", FUNCNAME, 'A' + m_index);
break;
case WR0_SEND_ABORT:
- LOGCMD("%s %s Ch:%c : Send abort command - not implemented\n", FUNCNAME, tag(), 'A' + m_index);
+ LOGCMD("%s Ch:%c : Send abort command\n", FUNCNAME, 'A' + m_index);
+ // TODO: what actually happens if you try this in a mode other than SDLC?
+ tx_setup(0xff, 8, 0, 1);
+
+ // FIXME: how does this interact with interrupts?
+ // For now assume it behaves like automatically sending CRC and generates a transmit interrupt when a new frame can be sent.
+ m_tx_special = 1;
+ m_rr0 |= RR0_TX_BUFFER_EMPTY;
+ m_rr1 &= ~RR1_ALL_SENT;
break;
case WR0_RESET_EXT_STATUS:
- // reset external/status interrupt
-
- m_rr0 &= ~(RR0_DCD | RR0_SYNC_HUNT | RR0_CTS | RR0_BREAK_ABORT);
- // release the latch
-
- m_rx_rr0_latch = 0;
- // update register to reflect wire values TODO: Check if this will fire new interrupts
- if (!m_dcd) m_rr0 |= RR0_DCD;
- if (m_sync) m_rr0 |= RR0_SYNC_HUNT;
- if (m_cts) m_rr0 |= RR0_CTS;
-
- // Clear any pending External interrupt
- m_uart->m_int_state[m_index == z80sio_device::CHANNEL_A ? 4 : 5] = 0;
-
- LOGINT("%s %s Ch:%c : Reset External/Status Interrupt\n", FUNCNAME, tag(), 'A' + m_index);
+ reset_ext_status();
+ LOGINT("%s Ch:%c : Reset External/Status Interrupt\n", FUNCNAME, 'A' + m_index);
break;
case WR0_CHANNEL_RESET:
// channel reset
- LOGCMD("%s %s Ch:%c : Channel Reset\n", FUNCNAME, tag(), 'A' + m_index);
+ LOGCMD("%s Ch:%c : Channel Reset\n", FUNCNAME, 'A' + m_index);
device_reset();
break;
case WR0_ENABLE_INT_NEXT_RX:
// enable interrupt on next receive character
- LOGINT("%s %s Ch:%c : Enable Interrupt on Next Received Character\n", FUNCNAME, tag(), 'A' + m_index);
+ LOGINT("%s Ch:%c : Enable Interrupt on Next Received Character\n", FUNCNAME, 'A' + m_index);
m_rx_first = 1;
break;
case WR0_RESET_TX_INT:
+ LOGCMD("%s Ch:%c : Reset Transmitter Interrupt Pending\n", FUNCNAME, 'A' + m_index);
// reset transmitter interrupt pending
- {
- uint8_t priority = 3; // Assume TxB
- // Check if it is TxA
- if (m_index == z80sio_device::CHANNEL_A)
- {
- // Check if priority bit is cleared
- priority = (m_uart->m_chanA->m_wr2 & z80sio_channel::WR2_PRIORITY) == 0 ? 1 : 2;
- }
- m_uart->m_int_state[priority] = 0;
- LOGINT("%s %s Ch:%c : Reset TX Interrupt, priority:%d\n", FUNCNAME, tag(), 'A' + m_index, priority);
- }
- m_uart->check_interrupts();
- LOGCMD("%s %s Ch:%c : Reset Transmitter Interrupt Pending\n", FUNCNAME, tag(), 'A' + m_index);
+ m_uart->clear_interrupt(m_index, INT_TRANSMIT);
break;
case WR0_ERROR_RESET:
// error reset
- LOGCMD("%s %s Ch:%c : Error Reset\n", FUNCNAME, tag(), 'A' + m_index);
+ LOGCMD("%s Ch:%c : Error Reset\n", FUNCNAME, 'A' + m_index);
if ((WR1_RX_INT_FIRST == (m_wr1 & WR1_RX_INT_MODE_MASK)) && (m_rr1 & (RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR)))
{
// clearing framing and overrun errors advances the FIFO
+ // TODO: Intel 8274 manual doesn't mention this behaviour - is it specific to Z80 SIO?
m_rr1 &= ~(RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR | RR1_PARITY_ERROR);
advance_rx_fifo();
}
@@ -1054,27 +1461,12 @@ void z80sio_channel::do_sioreg_wr0(uint8_t data)
}
break;
case WR0_RETURN_FROM_INT:
- LOGINT("%s %s Ch:%c : Return from interrupt\n", FUNCNAME, tag(), 'A' + m_index);
- {
- int found = 0;
- // loop over all interrupt sources
- for (auto & elem : m_uart->m_int_state)
- {
- // find the first channel with an interrupt requested
- if (elem & (Z80_DAISY_IEO))
- {
- // clear the IEO state and update the IRQs
- elem &= ~(Z80_DAISY_IEO);
- m_uart->check_interrupts();
- found = 1;
- break;
- }
- }
- LOGINT(" - %s\n", found == 0 ? "failed to find an interrupt to clear IEO on!" : "cleared IEO");
- }
+ LOGINT("%s Ch:%c : Return from interrupt\n", FUNCNAME, 'A' + m_index);
+ if (m_index == z80sio_device::CHANNEL_A)
+ m_uart->return_from_interrupt();
break;
default:
- LOG("Z80SIO \"%s\" Channel %c : Unsupported WR0 command %02x mask %02x\n", owner()->tag(), 'A' + m_index, data, WR0_REGISTER_MASK);
+ LOG("Z80SIO Channel %c : Unsupported WR0 command %02x mask %02x\n", 'A' + m_index, data, WR0_REGISTER_MASK);
}
do_sioreg_wr0_resets(data);
@@ -1109,16 +1501,6 @@ void z80sio_channel::do_sioreg_wr1(uint8_t data)
LOG("Z80SIO \"%s\" Channel %c : Receiver Interrupt on All Characters\n", owner()->tag(), 'A' + m_index);
break;
}
-
- if (data & WR1_WRDY_ENABLE)
- {
- if (data & WR1_WRDY_ON_RX_TX)
- set_ready((m_rr0 & RR0_RX_CHAR_AVAILABLE) != 0);
- else
- set_ready((m_rr0 & RR0_TX_BUFFER_EMPTY) != 0);
- }
- else
- set_ready(false);
}
void z80sio_channel::do_sioreg_wr2(uint8_t data)
@@ -1140,7 +1522,10 @@ void z80sio_channel::do_sioreg_wr4(uint8_t data)
m_wr4 = data;
LOG("Z80SIO \"%s\" Channel %c : Parity Enable %u\n", owner()->tag(), 'A' + m_index, (data & WR4_PARITY_ENABLE) ? 1 : 0);
LOG("Z80SIO \"%s\" Channel %c : Parity %s\n", owner()->tag(), 'A' + m_index, (data & WR4_PARITY_EVEN) ? "Even" : "Odd");
- LOG("Z80SIO \"%s\" Channel %c : Stop Bits %s\n", owner()->tag(), 'A' + m_index, stop_bits_tostring(get_stop_bits()));
+ if ((m_wr4 & WR4_STOP_BITS_MASK) == WR4_STOP_BITS_SYNC)
+ LOG("Z80SIO \"%s\" Channel %c : Synchronous Mode\n", owner()->tag(), 'A' + m_index);
+ else
+ LOG("Z80SIO \"%s\" Channel %c : Stop Bits %g\n", owner()->tag(), 'A' + m_index, (((m_wr4 & WR4_STOP_BITS_MASK) >> 2) + 1) / 2.);
LOG("Z80SIO \"%s\" Channel %c : Clock Mode %uX\n", owner()->tag(), 'A' + m_index, get_clock_mode());
}
@@ -1157,13 +1542,11 @@ void z80sio_channel::do_sioreg_wr5(uint8_t data)
void z80sio_channel::do_sioreg_wr6(uint8_t data)
{
LOG("Z80SIO \"%s\" Channel %c : Transmit Sync %02x\n", owner()->tag(), 'A' + m_index, data);
- m_sync = (m_sync & 0xff00) | data;
}
void z80sio_channel::do_sioreg_wr7(uint8_t data)
{
LOG("Z80SIO \"%s\" Channel %c : Receive Sync %02x\n", owner()->tag(), 'A' + m_index, data);
- m_sync = (data << 8) | (m_sync & 0xff);
}
//-------------------------------------------------
@@ -1181,7 +1564,7 @@ void z80sio_channel::control_write(uint8_t data)
m_wr0 &= ~WR0_REGISTER_MASK;
}
- LOG("\n%s(%02x) reg %02x\n", FUNCNAME, data, reg);
+ LOG("%s(%02x) reg %02x\n", FUNCNAME, data, reg);
switch (reg)
{
@@ -1189,8 +1572,8 @@ void z80sio_channel::control_write(uint8_t data)
case REG_WR1_INT_DMA_ENABLE: do_sioreg_wr1(data); m_uart->check_interrupts(); break;
case REG_WR2_INT_VECTOR: do_sioreg_wr2(data); break;
case REG_WR3_RX_CONTROL: do_sioreg_wr3(data); update_serial(); break;
- case REG_WR4_RX_TX_MODES: do_sioreg_wr4(data); update_serial(); break;
- case REG_WR5_TX_CONTROL: do_sioreg_wr5(data); update_serial(); update_rts(); break;
+ case REG_WR4_RX_TX_MODES: do_sioreg_wr4(data); update_serial(); update_dtr_rts_break(); break;
+ case REG_WR5_TX_CONTROL: do_sioreg_wr5(data); update_serial(); update_dtr_rts_break(); transmit_enable(); break;
case REG_WR6_SYNC_OR_SDLC_A: do_sioreg_wr6(data); break;
case REG_WR7_SYNC_OR_SDLC_F: do_sioreg_wr7(data); break;
default:
@@ -1206,11 +1589,15 @@ uint8_t z80sio_channel::data_read()
{
uint8_t const data = uint8_t(m_rx_data_fifo & 0x000000ffU);
- // framing and overrun errors need to be cleared to advance the FIFO in interrupt-on-first mode
- if ((WR1_RX_INT_FIRST != (m_wr1 & WR1_RX_INT_MODE_MASK)) || !(m_rr1 & (RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR)))
- advance_rx_fifo();
+ if (!machine().side_effect_disabled())
+ {
+ // framing and overrun errors need to be cleared to advance the FIFO in interrupt-on-first mode
+ // TODO: Intel 8274 manual doesn't mention this behaviour - is it specific to Z80 SIO?
+ if ((WR1_RX_INT_FIRST != (m_wr1 & WR1_RX_INT_MODE_MASK)) || !(m_rr1 & (RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR)))
+ advance_rx_fifo();
- LOG("Z80SIO \"%s\" Channel %c : Data Register Read '%02x'\n", owner()->tag(), 'A' + m_index, data);
+ LOG("Z80SIO \"%s\" Channel %c : Data Register Read '%02x'\n", owner()->tag(), 'A' + m_index, data);
+ }
return data;
}
@@ -1221,35 +1608,39 @@ uint8_t z80sio_channel::data_read()
//-------------------------------------------------
void z80sio_channel::data_write(uint8_t data)
{
+ if (!(m_rr0 & RR0_TX_BUFFER_EMPTY))
+ LOGTX("Z80SIO \"%s\" Channel %c : Dropped Data Byte '%02x'\n", owner()->tag(), 'A' + m_index, m_tx_data);
+
+ // fill transmit buffer
m_tx_data = data;
+ m_rr0 &= ~RR0_TX_BUFFER_EMPTY;
+ m_rr1 &= ~RR1_ALL_SENT;
- if ((m_wr5 & WR5_TX_ENABLE) && is_transmit_register_empty())
- {
- LOGTX("Z80SIO \"%s\" Channel %c : Transmit Data Byte '%02x'\n", owner()->tag(), 'A' + m_index, m_tx_data);
+ // handle automatic RTS
+ bool const async((m_wr4 & WR4_STOP_BITS_MASK) != WR4_STOP_BITS_SYNC);
+ if (async && !(m_wr5 & WR5_RTS))
+ set_rts(0); // TODO: if transmission is disabled when the data buffer is full, is this still asserted?
- transmit_register_setup(m_tx_data);
+ // clear transmit interrupt
+ m_uart->clear_interrupt(m_index, INT_TRANSMIT);
- // empty transmit buffer
- m_rr0 |= RR0_TX_BUFFER_EMPTY;
- if ((m_wr1 & WR1_WRDY_ENABLE) && !(m_wr1 & WR1_WRDY_ON_RX_TX))
- set_ready(true);
- if (m_wr1 & WR1_TX_INT_ENABLE)
- m_uart->trigger_interrupt(m_index, INT_TRANSMIT);
- }
- else
+ // may be possible to transmit immediately
+ if (!m_tx_bits && transmit_allowed())
{
- LOGTX(" Transmitter not %s, data byte %02x pending\n", m_wr5 & WR5_TX_ENABLE ? "emptied" : "enabled", m_tx_data);
- m_rr0 &= ~RR0_TX_BUFFER_EMPTY;
- if ((m_wr1 & WR1_WRDY_ENABLE) && !(m_wr1 & WR1_WRDY_ON_RX_TX))
- set_ready(false);
+ if (async)
+ {
+ async_tx_setup();
+ }
+ else
+ {
+ logerror("unsupported synchronous mode transmit\n");
+ }
}
-
- m_rr1 &= ~RR1_ALL_SENT;
}
//-------------------------------------------------
-// receive_reset - reset receive state
+// receive_reset - reset receiver state
//-------------------------------------------------
void z80sio_channel::receive_reset()
{
@@ -1272,14 +1663,24 @@ void z80sio_channel::advance_rx_fifo()
m_rx_error_fifo >>= 8;
// load error status from the FIFO
- m_rr1 = (m_rr1 & ~RR1_CRC_FRAMING_ERROR) | uint8_t(m_rx_error_fifo & 0x000000ffU);
+ m_rr1 = (m_rr1 & ~m_rr1_auto_reset) | uint8_t(m_rx_error_fifo & 0x000000ffU);
+
+ // if we're in interrupt-on-first mode, clear interrupt if there's no pending error condition
+ if ((m_wr1 & WR1_RX_INT_MODE_MASK) == WR1_RX_INT_FIRST)
+ {
+ for (int i = 0; m_rx_fifo_depth > i; ++i)
+ {
+ if (uint8_t(m_rx_error_fifo >> (i * 8)) & (RR1_CRC_FRAMING_ERROR | RR1_RX_OVERRUN_ERROR))
+ return;
+ }
+ m_uart->clear_interrupt(m_index, INT_RECEIVE);
+ }
}
else
{
// no more characters available in the FIFO
m_rr0 &= ~RR0_RX_CHAR_AVAILABLE;
- if ((m_wr1 & WR1_WRDY_ENABLE) && (m_wr1 & WR1_WRDY_ON_RX_TX))
- set_ready(false);
+ m_uart->clear_interrupt(m_index, INT_RECEIVE);
}
}
}
@@ -1327,8 +1728,6 @@ void z80sio_channel::receive_data()
}
m_rr0 |= RR0_RX_CHAR_AVAILABLE;
- if ((m_wr1 & WR1_WRDY_ENABLE) && (m_wr1 & WR1_WRDY_ON_RX_TX))
- set_ready(true);
if (!m_rx_fifo_depth)
m_rr1 |= uint8_t(rx_error);
@@ -1342,11 +1741,8 @@ void z80sio_channel::receive_data()
break;
case WR1_RX_INT_ALL_PARITY:
- m_uart->trigger_interrupt(m_index, (rx_error & (RR1_CRC_FRAMING_ERROR | RR1_PARITY_ERROR)) ? INT_SPECIAL : INT_RECEIVE);
- break;
-
case WR1_RX_INT_ALL:
- m_uart->trigger_interrupt(m_index, (rx_error & RR1_CRC_FRAMING_ERROR) ? INT_SPECIAL : INT_RECEIVE);
+ m_uart->trigger_interrupt(m_index, INT_RECEIVE);
break;
default:
@@ -1360,35 +1756,16 @@ void z80sio_channel::receive_data()
//-------------------------------------------------
WRITE_LINE_MEMBER( z80sio_channel::cts_w )
{
- LOG("%s(%02x) %s:%c\n",FUNCNAME, state, tag(), 'A' + m_index);
-
- if (m_cts != state)
+ if (bool(m_cts) != bool(state))
{
- // enable transmitter if in auto enables mode
- if (!state)
- if (m_wr3 & WR3_AUTO_ENABLES)
- m_wr5 |= WR5_TX_ENABLE;
+ LOGCTS("Z80SIO Channel %c : CTS %u\n", 'A' + m_index, state);
- // set clear to send
m_cts = state;
+ trigger_ext_int();
- if (!m_rx_rr0_latch)
- {
- if (!m_cts)
- m_rr0 |= RR0_CTS;
- else
- m_rr0 &= ~RR0_CTS;
-
- // trigger interrupt
- if (m_wr1 & WR1_EXT_INT_ENABLE)
- {
- // trigger interrupt
- m_uart->trigger_interrupt(m_index, INT_EXTERNAL);
-
- // latch read register 0
- m_rx_rr0_latch = 1;
- }
- }
+ // this may enable transmission
+ if (!state)
+ transmit_enable();
}
}
@@ -1398,34 +1775,12 @@ WRITE_LINE_MEMBER( z80sio_channel::cts_w )
//-------------------------------------------------
WRITE_LINE_MEMBER( z80sio_channel::dcd_w )
{
- LOG("Z80SIO \"%s\" Channel %c : DCD %u\n", owner()->tag(), 'A' + m_index, state);
-
- if (m_dcd != state)
+ if (bool(m_dcd) != bool(state))
{
- // enable receiver if in auto enables mode
- if (!state)
- if (m_wr3 & WR3_AUTO_ENABLES)
- m_wr3 |= WR3_RX_ENABLE;
+ LOG("Z80SIO Channel %c : DCD %u\n", 'A' + m_index, state);
- // set data carrier detect
m_dcd = state;
-
- if (!m_rx_rr0_latch)
- {
- if (!m_dcd)
- m_rr0 |= RR0_DCD;
- else
- m_rr0 &= ~RR0_DCD;
-
- if (m_wr1 & WR1_EXT_INT_ENABLE)
- {
- // trigger interrupt
- m_uart->trigger_interrupt(m_index, INT_EXTERNAL);
-
- // latch read register 0
- m_rx_rr0_latch = 1;
- }
- }
+ trigger_ext_int();
}
}
@@ -1435,29 +1790,15 @@ WRITE_LINE_MEMBER( z80sio_channel::dcd_w )
//-------------------------------------------------
WRITE_LINE_MEMBER( z80sio_channel::sync_w )
{
- LOG("Z80SIO \"%s\" Channel %c : Sync %u\n", owner()->tag(), 'A' + m_index, state);
-
- if (m_sh != state)
+ if (bool(m_sync) != bool(state))
{
- // set ring indicator state
- m_sh = state;
-
- if (!m_rx_rr0_latch)
- {
- if (m_sh)
- m_rr0 |= RR0_SYNC_HUNT;
- else
- m_rr0 &= ~RR0_SYNC_HUNT;
+ LOG("Z80SIO Channel %c : Sync %u\n", 'A' + m_index, state);
- if (m_wr1 & WR1_EXT_INT_ENABLE)
- {
- // trigger interrupt
- m_uart->trigger_interrupt(m_index, INT_EXTERNAL);
+ m_sync = state;
- // latch read register 0
- m_rx_rr0_latch = 1;
- }
- }
+ // sync is a general-purpose input in asynchronous mode
+ if ((m_wr4 & WR4_STOP_BITS_MASK) != WR4_STOP_BITS_SYNC)
+ trigger_ext_int();
}
}
@@ -1499,11 +1840,12 @@ WRITE_LINE_MEMBER( z80sio_channel::rxc_w )
// sample a data/parity/stop bit
if (!m_rxd)
m_rx_sr &= ~uint16_t(1U << (m_rx_bit - 1));
+ LOGBIT("%s() \"%s \"Channel %c Received Data Bit %d\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_rxd);
if ((get_rx_word_length() + ((m_wr4 & WR4_PARITY_ENABLE) ? 1 : 0) + 1) == m_rx_bit)
{
// this is the stop bit - framing error adds a half bit period
- m_rx_count = m_rxd ? 0 : clocks;
+ m_rx_count = m_rxd ? (clocks / 2) : clocks;
m_rx_bit = 0;
LOGRCV("%s() \"%s \"Channel %c Received Data %02x\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_rx_sr & 0xff);
@@ -1511,8 +1853,6 @@ WRITE_LINE_MEMBER( z80sio_channel::rxc_w )
}
else
{
- LOGBIT("%s() \"%s \"Channel %c Received Data Bit %d\n", FUNCNAME, owner()->tag(), 'A' + m_index, m_rxd);
-
// wait a whole bit period for the next bit
m_rx_count = clocks;
++m_rx_bit;
@@ -1534,18 +1874,92 @@ WRITE_LINE_MEMBER( z80sio_channel::rxc_w )
WRITE_LINE_MEMBER( z80sio_channel::txc_w )
{
//LOG("Z80SIO \"%s\" Channel %c : Transmitter Clock Pulse\n", owner()->tag(), m_index + 'A');
- int clocks = get_clock_mode();
- if (clocks == 1)
- tx_clock_w(state);
- else if(state)
+ if (!state && m_tx_clock)
{
- tx_clock_w(m_tx_clock < clocks/2);
+ // falling edge active
+ if (m_tx_count)
+ {
+ // divide transmit clock
+ --m_tx_count;
+ }
+ else if (!m_tx_bits)
+ {
+ // idle marking line
+ if (!m_txd)
+ {
+ m_txd = 1;
+ if (!(m_wr5 & WR5_SEND_BREAK))
+ out_txd_cb(1);
+ }
- m_tx_clock++;
- if (m_tx_clock == clocks)
- m_tx_clock = 0;
+ if (((m_wr4 & WR4_STOP_BITS_MASK) != WR4_STOP_BITS_SYNC) && (m_rr0 & RR0_TX_BUFFER_EMPTY))
+ {
+ // when the RTS bit is reset in asynchronous mode, the _RTS output goes high after the transmitter empties
+ if (!(m_wr5 & WR5_RTS) && !m_rts)
+ set_rts(1); // TODO: if transmission is disabled when the data buffer is full, is this still asserted?
+
+ // if transmit buffer is empty in asynchronous mode then all characters have been sent
+ m_rr1 |= RR1_ALL_SENT;
+ }
+ }
+ else
+ {
+ bool const sdlc_mode((m_wr4 & (WR4_STOP_BITS_MASK | WR4_SYNC_MODE_MASK)) == (WR4_STOP_BITS_SYNC | WR4_SYNC_MODE_SDLC));
+ bool const stuff_zero(sdlc_mode && !m_tx_framing && ((m_tx_hist & 0x1fU) == 0x1fU));
+ // have bits, shift out
+ int const db(stuff_zero ? 0 : BIT(m_tx_sr, 0));
+ if (!stuff_zero)
+ {
+ LOGBIT("%s() Channel %c transmit %s bit %d m_wr5:%02x\n", FUNCNAME, 'A' + m_index, m_tx_framing ? "framing" : "data", db, m_wr5);
+ if (m_tx_parity >= m_tx_bits)
+ m_tx_parity = 0;
+ else if (m_tx_parity)
+ m_tx_sr ^= uint16_t(db) << (m_tx_bits - m_tx_parity);
+ m_tx_sr >>= 1;
+
+ if ((m_wr5 & WR5_TX_CRC_ENABLE) & !m_tx_framing)
+ {
+ uint16_t const poly((m_wr5 & WR5_CRC16) ? 0x8005U : device_sdlc_consumer_interface::POLY_SDLC);
+ m_tx_crc = device_sdlc_consumer_interface::update_frame_check(poly, m_tx_crc, db);
+ }
+ }
+ else
+ {
+ LOGBIT("%s() Channel %c stuff bit %d m_wr5:%02x\n", FUNCNAME, 'A' + m_index, db, m_wr5);
+ }
+ m_tx_hist = (m_tx_hist << 1) | db;
+
+ // update output line state
+ if (bool(m_txd) != bool(db))
+ {
+ m_txd = db;
+ if (!(m_wr5 & WR5_SEND_BREAK))
+ out_txd_cb(m_txd);
+ }
+
+ // calculate next bit time
+ m_tx_count = get_clock_mode();
+ if (!stuff_zero && !--m_tx_bits)
+ {
+ switch (m_wr4 & WR4_STOP_BITS_MASK)
+ {
+ case WR4_STOP_BITS_SYNC:
+ case WR4_STOP_BITS_1:
+ break;
+ case WR4_STOP_BITS_1_5:
+ m_tx_count = ((m_tx_count * 3) + 1) / 2; // TODO: what does 1.5 stop bits do in TxC/1 mode? the +1 here rounds it up
+ break;
+ case WR4_STOP_BITS_2:
+ m_tx_count *= 2;
+ break;
+ }
+ transmit_complete();
+ }
+ --m_tx_count;
+ }
}
+ m_tx_clock = state;
}
@@ -1554,39 +1968,14 @@ WRITE_LINE_MEMBER( z80sio_channel::txc_w )
//-------------------------------------------------
void z80sio_channel::update_serial()
{
- int const data_bit_count = get_rx_word_length();
- stop_bits_t const stop_bits = get_stop_bits();
- parity_t parity;
+ // FIXME: changing parameters doesn't actually reset the receive state, this is legacy from working around diserial
LOG("%s\n", FUNCNAME);
if (m_wr4 & WR4_PARITY_ENABLE)
- {
LOG("- Parity enabled\n");
- if (m_wr4 & WR4_PARITY_EVEN)
- parity = PARITY_EVEN;
- else
- parity = PARITY_ODD;
- }
- else
- parity = PARITY_NONE;
-
- set_data_frame(1, data_bit_count, parity, stop_bits);
receive_reset(); // if stop bits is changed from 0, receive register has to be reset
}
-//-------------------------------------------------
-// set_dtr -
-//-------------------------------------------------
-void z80sio_channel::set_dtr(int state)
-{
- LOG("%s(%d)\n", FUNCNAME, state);
- m_dtr = state;
-
- if (m_index == z80sio_device::CHANNEL_A)
- m_uart->m_out_dtra_cb(m_dtr);
- else
- m_uart->m_out_dtrb_cb(m_dtr);
-}