diff options
author | 2017-11-15 03:41:39 +1100 | |
---|---|---|
committer | 2017-11-16 22:22:20 +1100 | |
commit | 46c4dfb5f91509491306e9d502eea3a290ba951b (patch) | |
tree | cdc5b1afc8dbdc9707e8650983c802de00c5484f | |
parent | 4c9bac79ff0cf7aa1b87048485ed8ea77dea8051 (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.
-rw-r--r-- | scripts/src/machine.lua | 2 | ||||
-rw-r--r-- | src/devices/machine/sdlc.cpp | 298 | ||||
-rw-r--r-- | src/devices/machine/sdlc.h | 116 | ||||
-rw-r--r-- | src/devices/machine/z80sio.cpp | 1457 | ||||
-rw-r--r-- | src/devices/machine/z80sio.h | 267 |
5 files changed, 1433 insertions, 707 deletions
diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index d6f08b7c8ce..cedd7a02e55 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -29,6 +29,8 @@ files { MAME_DIR .. "src/devices/machine/ram.h", MAME_DIR .. "src/devices/machine/legscsi.cpp", MAME_DIR .. "src/devices/machine/legscsi.h", + MAME_DIR .. "src/devices/machine/sdlc.cpp", + MAME_DIR .. "src/devices/machine/sdlc.h", MAME_DIR .. "src/devices/machine/terminal.cpp", MAME_DIR .. "src/devices/machine/terminal.h", MAME_DIR .. "src/devices/machine/timer.cpp", diff --git a/src/devices/machine/sdlc.cpp b/src/devices/machine/sdlc.cpp new file mode 100644 index 00000000000..b78b5dd0055 --- /dev/null +++ b/src/devices/machine/sdlc.cpp @@ -0,0 +1,298 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb + +#include "emu.h" +#include "sdlc.h" + +#include <sstream> + + +#define LOG_GENERAL (1U << 0) +#define LOG_RXBIT (1U << 1) +#define LOG_RXFLAG (1U << 2) +#define LOG_LINESTATE (1U << 3) +#define LOG_FRAMING (1U << 4) + +//#define VERBOSE (LOG_GENERAL | LOG_RXBIT | LOG_RXFLAG | LOG_LINESTATE | LOG_FRAMING) +#include "logmacro.h" + +#define LOGRXBIT(...) LOGMASKED(LOG_RXBIT, __VA_ARGS__) +#define LOGRXFLAG(...) LOGMASKED(LOG_RXFLAG, __VA_ARGS__) +#define LOGLINESTATE(...) LOGMASKED(LOG_LINESTATE, __VA_ARGS__) +#define LOGFRAMING(...) LOGMASKED(LOG_FRAMING, __VA_ARGS__) + + +DEFINE_DEVICE_TYPE(SDLC_LOGGER, sdlc_logger_device, "sdlc_logger", "SDLC/HDLC logger") + + +constexpr std::uint16_t device_sdlc_consumer_interface::POLY_SDLC; + + +device_sdlc_consumer_interface::device_sdlc_consumer_interface(machine_config const &mconfig, device_t &device) : + device_interface(device, "sdlc_consumer"), + m_line_active(0U), + m_discard_bits(0U), + m_in_frame(0U), + m_shift_register(0xffffU), + m_frame_check(0xffffU) +{ +} + +void device_sdlc_consumer_interface::interface_post_start() +{ + device().save_item(NAME(m_line_active)); + device().save_item(NAME(m_discard_bits)); + device().save_item(NAME(m_in_frame)); + device().save_item(NAME(m_shift_register)); + device().save_item(NAME(m_frame_check)); +} + +void device_sdlc_consumer_interface::rx_bit(bool state) +{ + LOGRXBIT("Received bit %u\n", state ? 1U : 0U); + + m_shift_register = (m_shift_register >> 1) | (state ? 0x8000U : 0x0000U); + if (!state && !m_line_active) + { + // any zero bit means the line has become active + LOGLINESTATE("Line became active\n"); + m_line_active = 1U; + line_active(); + } + + if ((m_shift_register & 0xff00U) == 0x7e00U) + { + // a flag opens and closes frames + LOGRXFLAG("Received flag\n"); + if (m_in_frame) + { + LOGFRAMING("End of frame\n"); + m_in_frame = 0U; + frame_end(); + } + m_discard_bits = 8U; + m_frame_check = 0xffffU; + } + else if ((m_shift_register & 0xfffeU) == 0xfffeU) + { + // fifteen consecutive ones is an inactive line condition + if (m_line_active) + { + LOGLINESTATE("Line became inactive\n"); + m_line_active = 0U; + line_inactive(); + } + } + else if ((m_shift_register & 0xfe00U) == 0xfe00U) + { + // seven consecutive ones is a frame abort + if (m_in_frame || m_discard_bits) + { + LOGFRAMING("Received frame abort\n"); + m_in_frame = 0U; + m_discard_bits = 0U; + frame_abort(); + } + } + else + { + // discard the flag as it shifts off + if (m_discard_bits && !--m_discard_bits) + { + LOGFRAMING("Start of frame\n"); + m_in_frame = 1U; + frame_start(); + } + + // discard a zero after five consecutive ones + if (m_in_frame && ((m_shift_register & 0x01f8U) != 0x00f8U)) + { + bool const bit(BIT(m_shift_register, 8)); + m_frame_check = update_frame_check(POLY_SDLC, m_frame_check, bit); + data_bit(bit); + } + } +} + +void device_sdlc_consumer_interface::rx_reset() +{ + LOG("Receive reset\n"); + + m_line_active = 0U; + m_in_frame = 0U; + m_discard_bits = 0U; + m_shift_register = 0xffffU; + m_frame_check = 0xffffU; +} + + +sdlc_logger_device::sdlc_logger_device(machine_config const &mconfig, char const *tag, device_t *owner, std::uint32_t clock) : + device_t(mconfig, SDLC_LOGGER, tag, owner, clock), + device_sdlc_consumer_interface(mconfig, *this), + m_data_nrzi(0U), + m_clock_active(1U), + m_current_data(1U), + m_last_data(1U), + m_current_clock(1U), + m_frame_bits(0U), + m_buffer() +{ +} + +WRITE_LINE_MEMBER(sdlc_logger_device::clock_w) +{ + if (bool(state) != bool(m_current_clock)) + { + m_current_clock = state ? 1U : 0U; + if (m_current_clock == m_clock_active) + { + bool const bit(m_data_nrzi ? (m_current_data == m_last_data) : m_current_data); + LOGRXBIT("Received bit: %u (%u -> %u)\n", bit ? 1U : 0U, m_last_data, m_current_data); + m_last_data = m_current_data; + rx_bit(bit); + } + } +} + +void sdlc_logger_device::device_start() +{ + m_buffer.reset(new std::uint8_t[BUFFER_BYTES]); + + save_item(NAME(m_data_nrzi)); + save_item(NAME(m_clock_active)); + save_item(NAME(m_current_data)); + save_item(NAME(m_last_data)); + save_item(NAME(m_current_clock)); + save_item(NAME(m_frame_bits)); + save_pointer(NAME(m_buffer.get()), BUFFER_BYTES); +} + +void sdlc_logger_device::device_reset() +{ +} + +void sdlc_logger_device::frame_start() +{ + m_frame_bits = 0U; + m_expected_fcs = 0xffffU; +} + +void sdlc_logger_device::frame_end() +{ + shift_residual_bits(); + log_frame(false); +} + +void sdlc_logger_device::frame_abort() +{ + logerror("Frame aborted!\n"); + shift_residual_bits(); + log_frame(true); +} + +void sdlc_logger_device::data_bit(bool value) +{ + if (BUFFER_BITS > m_frame_bits) + { + m_buffer[m_frame_bits >> 3] >>= 1; + m_buffer[m_frame_bits >> 3] |= value ? 0x80U : 0x00U; + } + else if (BUFFER_BITS == m_frame_bits) + { + logerror("Frame buffer overrun!\n"); + } + + if ((16U <= m_frame_bits) && ((BUFFER_BITS + 16U) > m_frame_bits)) + m_expected_fcs = update_frame_check(POLY_SDLC, m_expected_fcs, BIT(m_buffer[(m_frame_bits - 16U) >> 3], m_frame_bits & 0x0007U)); + + ++m_frame_bits; +} + +void sdlc_logger_device::shift_residual_bits() +{ + if (BUFFER_BITS > m_frame_bits) + { + uint32_t const residual_bits(m_frame_bits & 0x0007U); + if (residual_bits) + m_buffer[m_frame_bits >> 3] >>= 8 - residual_bits; + } +} + +void sdlc_logger_device::log_frame(bool partial) const +{ + if (m_frame_bits) + { + std::ostringstream msg; + std::uint32_t const frame_bytes(m_frame_bits >> 3); + std::uint32_t const residual_bits(m_frame_bits & 0x0007U); + util::stream_format(msg, "Received %u-bit %sframe (%u bytes + %u bits)", m_frame_bits, partial ? "partial " : "", frame_bytes, residual_bits); + + if (8U <= m_frame_bits) + { + std::uint8_t const addr(m_buffer[0]); + util::stream_format(msg, " A=%02X%s", addr, (0xffU == addr) ? " (broadcast)" : !addr ? " (no station)" : ""); + } + + if (16U <= m_frame_bits) + { + std::uint8_t const ctrl(m_buffer[1]); + if (!BIT(ctrl, 0)) + { + msg << " I"; + } + else if (!BIT(ctrl, 1)) + { + msg << " S"; + switch (ctrl & 0x0cU) + { + case 0x00U: msg << " RR"; break; + case 0x04U: msg << " RNR"; break; + case 0x08U: msg << " REJ"; break; + } + } + else + { + msg << " U"; + switch (ctrl & 0xecU) + { + case 0x00U: msg << " UI"; break; + case 0x04U: msg << " RIM/SIM"; break; + case 0x0cU: msg << " DM"; break; + case 0x20U: msg << " UP"; break; + case 0x40U: msg << " DISC/RD"; break; + case 0x60U: msg << " UA"; break; + case 0x80U: msg << " SNRM"; break; + case 0x84U: msg << " FRMR"; break; + case 0x9cU: msg << " XID"; break; + case 0xc4U: msg << " CFGR"; break; + case 0xccU: msg << " SNRME"; break; + case 0xe0U: msg << " TEST"; break; + case 0xecU: msg << " BCN"; break; + } + } + + if (!partial && (BUFFER_BITS >= m_frame_bits)) + { + std::uint16_t fcs; + fcs = std::uint16_t(m_buffer[frame_bytes - 2]) >> residual_bits; + fcs |= std::uint16_t(m_buffer[frame_bytes - 1]) << (8 - residual_bits); + if (residual_bits) + fcs |= (std::uint16_t(m_buffer[frame_bytes]) & ((1U << residual_bits) - 1U)) << (16 - residual_bits); + fcs = ~BITSWAP16(fcs, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); + util::stream_format(msg, " FCS=%04X", fcs); + if (!is_frame_check_good()) + util::stream_format(msg, " (expected %04X)", m_expected_fcs); + } + } + + if (!partial) + msg << (is_frame_check_good() ? " (good)" : " (bad)"); + + for (std::uint32_t i = 0U; (frame_bytes > i) && (BUFFER_BYTES > i); ++i) + util::stream_format(msg, (i & 0x000fU) ? " %02X" : "\n %02X", m_buffer[i]); + if (residual_bits && (BUFFER_BITS >= m_frame_bits)) + util::stream_format(msg, (residual_bits > 4) ? "%s %02X&%02X" : "%s %01X&%01X", (frame_bytes & 0x000fU) ? "" : "\n ", m_buffer[frame_bytes], (1U << residual_bits) - 1); + + logerror("%s\n", msg.str()); + } +} diff --git a/src/devices/machine/sdlc.h b/src/devices/machine/sdlc.h new file mode 100644 index 00000000000..1b9b8efa832 --- /dev/null +++ b/src/devices/machine/sdlc.h @@ -0,0 +1,116 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +#ifndef MAME_MACHINE_SDLC_H +#define MAME_MACHINE_SDLC_H + +#pragma once + +#include <cstdint> +#include <memory> +#include <utility> + + +#define MCFG_SDLC_LOGGER_DATA_NRZL \ + downcast<sdlc_logger_device &>(*device).clock_active(0); + +#define MCFG_SDLC_LOGGER_DATA_NRZI \ + downcast<sdlc_logger_device &>(*device).clock_active(1); + +#define MCFG_SDLC_LOGGER_CLOCK_ACTIVE_RISING \ + downcast<sdlc_logger_device &>(*device).clock_active(1); + +#define MCFG_SDLC_LOGGER_CLOCK_ACTIVE_FALLING \ + downcast<sdlc_logger_device &>(*device).clock_active(0); + + +class device_sdlc_consumer_interface : public device_interface +{ +public: + static constexpr std::uint16_t POLY_SDLC = 0x1021U; + + template <typename T> + static constexpr u16 update_frame_check(u16 poly, u16 current, T bit) + { + return (current << 1) ^ ((bool(bit) != bool(BIT(current, 15))) ? poly : 0U); + } + +protected: + device_sdlc_consumer_interface(machine_config const &mconfig, device_t &device); + + virtual void interface_post_start() override; + + void rx_bit(bool state); + void rx_reset(); + + bool is_line_active() const { return bool(m_line_active); } + uint16_t get_frame_check() const { return m_frame_check; } + bool is_frame_check_good() const { return 0x1d0fU == m_frame_check; } + +private: + template <typename... Params> void logerror(Params &&... args) { device().logerror(std::forward<Params>(args)...); } + + virtual void frame_start() { } + virtual void frame_end() { } + virtual void frame_abort() { } + virtual void line_active() { } + virtual void line_inactive() { } + virtual void data_bit(bool value) { } + + std::uint8_t m_line_active; + std::uint8_t m_discard_bits; + std::uint8_t m_in_frame; + std::uint16_t m_shift_register; + std::uint16_t m_frame_check; +}; + + +class sdlc_logger_device : public device_t, public device_sdlc_consumer_interface +{ +public: + sdlc_logger_device(machine_config const &mconfig, char const *tag, device_t *owner, std::uint32_t clock); + + // input signals + DECLARE_WRITE_LINE_MEMBER(data_w) { m_current_data = state ? 1U : 0U; } + DECLARE_WRITE_LINE_MEMBER(clock_w); + + // input format configuration + DECLARE_WRITE_LINE_MEMBER(data_nrzi) { m_data_nrzi = state ? 1U : 0U; } + DECLARE_WRITE_LINE_MEMBER(clock_active) { m_clock_active = state ? 1U : 0U; } + +protected: + virtual void device_start() override; + virtual void device_reset() override; + + using device_t::logerror; + +private: + enum : std::size_t + { + BUFFER_BYTES = 1024U, + BUFFER_BITS = BUFFER_BYTES << 3 + }; + + virtual void frame_start() override; + virtual void frame_end() override; + virtual void frame_abort() override; + virtual void data_bit(bool value) override; + + void shift_residual_bits(); + void log_frame(bool partial) const; + + std::uint8_t m_data_nrzi; + std::uint8_t m_clock_active; + + std::uint8_t m_current_data; + std::uint8_t m_last_data; + std::uint8_t m_current_clock; + + std::uint32_t m_frame_bits; + std::uint16_t m_expected_fcs; + std::unique_ptr<std::uint8_t []> m_buffer; +}; + + +DECLARE_DEVICE_TYPE(SDLC_LOGGER, sdlc_logger_device) + +#endif // MAME_MACHINE_SDLC_H 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); -} diff --git a/src/devices/machine/z80sio.h b/src/devices/machine/z80sio.h index 06d65128b76..782ea9bcb0e 100644 --- a/src/devices/machine/z80sio.h +++ b/src/devices/machine/z80sio.h @@ -126,17 +126,15 @@ class z80sio_device; -class z80sio_channel : public device_t, public device_serial_interface +class z80sio_channel : public device_t { friend class z80sio_device; + friend class i8274_new_device; + friend class upd7201_new_device; public: z80sio_channel(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); - // device_serial_interface overrides - virtual void tra_callback() override; - virtual void tra_complete() override; - // read register handlers uint8_t do_sioreg_rr0(); uint8_t do_sioreg_rr1(); @@ -160,8 +158,6 @@ public: void data_write(uint8_t data); void receive_reset(); - void receive_data(); - void advance_rx_fifo(); DECLARE_WRITE_LINE_MEMBER( write_rx ) { m_rxd = state; } DECLARE_WRITE_LINE_MEMBER( cts_w ); @@ -174,7 +170,6 @@ public: // read registers enum uint8_t m_rr0; // REG_RR0_STATUS uint8_t m_rr1; // REG_RR1_SPEC_RCV_COND - uint8_t m_rr2; // REG_RR2_INTERRUPT_VECT // write registers enum uint8_t m_wr0; // REG_WR0_COMMAND_REGPT uint8_t m_wr1; // REG_WR1_INT_DMA_ENABLE @@ -185,15 +180,12 @@ public: uint8_t m_wr6; // REG_WR6_SYNC_OR_SDLC_A uint8_t m_wr7; // REG_WR7_SYNC_OR_SDLC_F - int m_variant; // Set in device - protected: enum { INT_TRANSMIT = 0, INT_EXTERNAL, - INT_RECEIVE, - INT_SPECIAL + INT_RECEIVE }; enum @@ -224,136 +216,13 @@ protected: REG_WR7_SYNC_OR_SDLC_F = 7 }; - enum - { - 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 - { - 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 - { - RR2_INT_VECTOR_MASK = 0xff, - RR2_INT_VECTOR_V1 = 0x02, - RR2_INT_VECTOR_V2 = 0x04, - RR2_INT_VECTOR_V3 = 0x08 - }; - - enum - { - 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 - { - 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, - WR1_WRDY_FUNCTION = 0x40, // WAIT not supported - WR1_WRDY_ENABLE = 0x80 - }; - - enum - { - 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, // not supported - 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, // not supported - WR2_PIN10_SYNDETB_RTSB = 0x80 // not supported - }; - - enum - { - 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, // not 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 - { - WR4_PARITY_ENABLE = 0x01, - WR4_PARITY_EVEN = 0x02, - WR4_STOP_BITS_MASK = 0x0c, - WR4_STOP_BITS_1 = 0x04, - WR4_STOP_BITS_1_5 = 0x08, // not supported - WR4_STOP_BITS_2 = 0x0c, - WR4_SYNC_MODE_MASK = 0x30, // not supported - WR4_SYNC_MODE_8_BIT = 0x00, // not supported - WR4_SYNC_MODE_16_BIT = 0x10, // not supported - WR4_SYNC_MODE_SDLC = 0x20, // not 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 - { - WR5_TX_CRC_ENABLE = 0x01, // not supported - WR5_RTS = 0x02, - WR5_CRC16 = 0x04, // not supported - 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 - }; + z80sio_channel( + const machine_config &mconfig, + device_type type, + const char *tag, + device_t *owner, + uint32_t clock, + uint8_t rr1_auto_reset); // device-level overrides virtual void device_resolve_objects() override; @@ -361,15 +230,14 @@ protected: virtual void device_reset() override; void update_serial(); - void update_rts(); + void update_dtr_rts_break(); void set_dtr(int state); void set_rts(int state); - void set_ready(bool ready); int get_clock_mode(); - stop_bits_t get_stop_bits(); int get_rx_word_length(); - int get_tx_word_length(); + int get_tx_word_length() const; + int get_tx_word_length(uint8_t data) const; // receiver state int m_rx_fifo_depth; @@ -387,21 +255,66 @@ protected: int m_rxd; int m_sh; // sync hunt - int m_cts; // clear to send latch - int m_dcd; // data carrier detect latch // transmitter state - uint8_t m_tx_data; // transmit data register - int m_tx_clock; // transmit clock pulse count - + uint8_t m_tx_data; + + int m_tx_clock; // transmit clock line state + int m_tx_count; // clocks until next bit transition + int m_tx_bits; // remaining bits in shift register + int m_tx_parity; // parity enable/disable + int m_tx_framing; // currnetly transmitting framing bits + int m_tx_special; // special conditions (SDLC FCS/abort) + uint16_t m_tx_sr; // transmit shift register + uint16_t m_tx_crc; // calculated transmit checksum + uint8_t m_tx_hist; // transmit history (for bitstuffing) + + int m_txd; int m_dtr; // data terminal ready int m_rts; // request to send + // external/status monitoring + int m_ext_latched; // changed data lines + int m_cts; // clear to send line state + int m_dcd; // data carrier detect line state + int m_sync; // sync line state + // synchronous state - uint16_t m_sync; // sync character int m_index; z80sio_device *m_uart; + +private: + // helpers + void out_txd_cb(int state); + void out_rts_cb(int state); + void out_dtr_cb(int state); + bool transmit_allowed() const; + + void receive_data(); + void advance_rx_fifo(); + + void transmit_enable(); + void transmit_complete(); + void async_tx_setup(); + void sdlc_tx_sr_empty(); + void tx_setup(uint16_t data, int bits, int parity, int framing); + void tx_setup_flag(); + + void reset_ext_status(); + void read_ext(); + void trigger_ext_int(); + + uint8_t const m_rr1_auto_reset; +}; + + +// ======================> i8274_channel + +class i8274_channel : public z80sio_channel +{ +public: + i8274_channel(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; @@ -454,7 +367,7 @@ public: DECLARE_WRITE8_MEMBER( cb_w ) { m_chanB->control_write(data); } // interrupt acknowledge - int m1_r(); + virtual int m1_r(); DECLARE_WRITE_LINE_MEMBER( rxa_w ) { m_chanA->write_rx(state); } DECLARE_WRITE_LINE_MEMBER( rxb_w ) { m_chanB->write_rx(state); } @@ -471,7 +384,7 @@ public: DECLARE_WRITE_LINE_MEMBER( syncb_w ) { m_chanB->sync_w(state); } protected: - z80sio_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock, uint32_t variant); + z80sio_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); // device-level overrides virtual void device_resolve_objects() override; @@ -487,18 +400,13 @@ protected: // internal interrupt management void check_interrupts(); void reset_interrupts(); - int get_interrupt_prio(int index, int type); - uint8_t modify_vector(int index, int type); - void trigger_interrupt(int index, int state); - int get_channel_index(z80sio_channel *ch) { return (ch == m_chanA) ? 0 : 1; } + void trigger_interrupt(int index, int type); + void clear_interrupt(int index, int type); + void return_from_interrupt(); + virtual uint8_t read_vector(); + virtual int const *interrupt_priorities() const; - // CPU types that has slightly different behaviour - enum - { - TYPE_Z80SIO = 0x001, - TYPE_UPD7201 = 0x002, - TYPE_I8274 = 0x004 - }; + int get_channel_index(z80sio_channel const *ch) const { return (ch == m_chanA) ? 0 : 1; } enum { @@ -530,26 +438,39 @@ protected: int m_int_state[8]; // interrupt state int m_int_source[8]; // interrupt source - int m_variant; const char *m_cputag; }; -class upd7201_new_device : public z80sio_device +class i8274_new_device : public z80sio_device { public: - upd7201_new_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + i8274_new_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + virtual int m1_r() override; + +protected: + i8274_new_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, uint32_t clock); + + // device_t overrides + virtual void device_add_mconfig(machine_config &config) override; + + // device_z80daisy_interface overrides + virtual int z80daisy_irq_ack() override; + virtual void z80daisy_irq_reti() override; + + virtual uint8_t read_vector() override; + virtual int const *interrupt_priorities() const override; }; -class i8274_new_device : public z80sio_device +class upd7201_new_device : public i8274_new_device { public: - i8274_new_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + upd7201_new_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); }; -// device type definition +// device type declaration DECLARE_DEVICE_TYPE(Z80SIO, z80sio_device) -DECLARE_DEVICE_TYPE(Z80SIO_CHANNEL, z80sio_channel) -DECLARE_DEVICE_TYPE(UPD7201_NEW, upd7201_new_device) DECLARE_DEVICE_TYPE(I8274_NEW, i8274_new_device) +DECLARE_DEVICE_TYPE(UPD7201_NEW, upd7201_new_device) #endif // MAME_MACHINE_Z80SIO_H |