From cea5bbace487abdaa98256d794be347192873b19 Mon Sep 17 00:00:00 2001 From: Patrick Mackinlay Date: Thu, 18 Oct 2018 18:00:02 +0700 Subject: z8038: new device (nw) Zilog Z8038 FIO (FIFO Input/Output Interface Unit). Used to drive the parallel/printer port on the MIPS Rx2030. Passes basic diagnostic tests, but further work depends on progress in the mips.cpp driver. --- scripts/src/machine.lua | 12 + scripts/target/mame/mess.lua | 1 + src/devices/machine/z8038.cpp | 700 ++++++++++++++++++++++++++++++++++++++++++ src/devices/machine/z8038.h | 229 ++++++++++++++ 4 files changed, 942 insertions(+) create mode 100644 src/devices/machine/z8038.cpp create mode 100644 src/devices/machine/z8038.h diff --git a/scripts/src/machine.lua b/scripts/src/machine.lua index 03ee38c0c90..ce7c2813b8f 100644 --- a/scripts/src/machine.lua +++ b/scripts/src/machine.lua @@ -3795,3 +3795,15 @@ if (MACHINES["SUN4C_MMU"]~=null) then MAME_DIR .. "src/devices/machine/sun4c_mmu.h", } end + +--------------------------------------------------- +-- +--@src/devices/machine/z8038.h,MACHINES["Z8038"] = true +--------------------------------------------------- + +if (MACHINES["Z8038"]~=null) then + files { + MAME_DIR .. "src/devices/machine/z8038.cpp", + MAME_DIR .. "src/devices/machine/z8038.h", + } +end diff --git a/scripts/target/mame/mess.lua b/scripts/target/mame/mess.lua index d0f914e1034..72c2dc1aace 100644 --- a/scripts/target/mame/mess.lua +++ b/scripts/target/mame/mess.lua @@ -671,6 +671,7 @@ MACHINES["IOPDMA"] = true MACHINES["IOPINTC"] = true MACHINES["IOPSIO2"] = true MACHINES["IOPTIMER"] = true +MACHINES["Z8038"] = true -------------------------------------------------- -- specify available bus cores diff --git a/src/devices/machine/z8038.cpp b/src/devices/machine/z8038.cpp new file mode 100644 index 00000000000..7cd55fb6d45 --- /dev/null +++ b/src/devices/machine/z8038.cpp @@ -0,0 +1,700 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +/* + * An emulation of the Zilog Z8038 FIO FIFO Input/Output Interface Unit. + * + * Sources: + * + * http://datasheet.datasheetarchive.com/originals/scans/Scans-98/DSAIHSC00090399.pdf + * + * The external interface uses port number 1 and 2 per the documentation, while + * the implementation uses port number 0 and 1 for convenience. + * + * TODO + * - more i/o lines and handshake + * - Z-BUS interrupt/acknowledge + * - dma cycles + * - fifo save state + */ + +#include "emu.h" +#include "z8038.h" + +#define LOG_GENERAL (1U << 0) +#define LOG_REG (1U << 1) +#define LOG_FIFO (1U << 2) +#define LOG_INT (1U << 3) + +//#define VERBOSE (LOG_GENERAL|LOG_REG|LOG_FIFO|LOG_INT) + +#include "logmacro.h" + +DEFINE_DEVICE_TYPE(Z8038, z8038_device, "z8038", "FIFO Input/Output Interface Unit") + +z8038_device::z8038_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) + : device_t(mconfig, Z8038, tag, owner, clock) + , m_out_int_cb{ *this, *this } + , m_out_E_cb(*this) + , m_out_F_cb(*this) + , m_out_H_cb(*this) + , m_out_J_cb(*this) +{ +} + +template void z8038_device::zbus_map(address_map &map) +{ + map(0x0, 0xf).rw(FUNC(z8038_device::zbus_reg_r), FUNC(z8038_device::zbus_reg_w)); + + // port 2 can not write to control register 2 + if (Port == 2) + map(0x9, 0x9).unmapw(); + + // Message In register is read-only + map(0xc, 0xc).unmapw(); +} + +// instantiate maps for port 1 and 2 +template void z8038_device::zbus_map<1>(address_map &map); +template void z8038_device::zbus_map<2>(address_map &map); + +void z8038_device::device_start() +{ + m_out_int_cb[0].resolve_safe(); + m_out_int_cb[1].resolve_safe(); + + m_out_E_cb.resolve_safe(); + m_out_F_cb.resolve_safe(); + m_out_H_cb.resolve_safe(); + m_out_J_cb.resolve_safe(); + + save_item(NAME(m_control_2)); + save_item(NAME(m_control_3)); + + for (u8 port = 0; port < 2; port++) + { + save_item(m_port[port].reg_state, "state", port + 1); + save_item(m_port[port].reg_pointer, "pointer", port + 1); + save_item(m_port[port].int_code, "int_code", port + 1); + save_item(m_port[port].int_asserted, "int_asserted", port + 1); + + save_item(m_port[port].control_0, "control_0", port + 1); + save_item(m_port[port].control_1, "control_1", port + 1); + save_item(m_port[port].interrupt_status, "interrupt_status", port + 1); + save_item(m_port[port].interrupt_vector, "interrupt_vector", port + 1); + save_item(m_port[port].byte_count, "byte_count", port + 1); + save_item(m_port[port].byte_count_comparison, "byte_count_comparison", port + 1); + save_item(m_port[port].message_in, "message_in", port + 1); + save_item(m_port[port].pattern_match, "pattern_match", port + 1); + save_item(m_port[port].pattern_mask, "pattern_mask", port + 1); + save_item(m_port[port].data_buffer, "data_buffer", port + 1); + } + + //save_item(NAME(m_fifo)); + + m_int_check = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(z8038_device::int_check), this)); + + // suppress startup interrupt line changes + m_port[0].int_asserted = false; + m_port[1].int_asserted = false; +} + +void z8038_device::device_reset() +{ + m_control_2 = 0; + + for (u8 port = 0; port < 2; port++) + { + m_port[port].reg_state = 0; + m_port[port].reg_pointer = 0; + m_port[port].int_code = 0; + m_port[port].control_0 = CR0_RESET; + set_int_state(port, false); + } +} + +u8 z8038_device::reg_r(u8 const port) +{ + /* + * The Port 2 CPU can determine when it is enabled by reading its Control + * Register 0, which is read as a "floating" data bus if not enabled or as + * 01H if enabled. + * + * FIXME: unsure what value to return for floating data bus + */ + if (port && !(m_control_2 & CR2_P2EN)) + return 0xff; + + // FIXME: reads from port 2 in i/o mode + if (port && (m_port[port].control_0 & CR0_P2M_IO)) + fatalerror("unexpected register read from Port 2 in i/o mode\n"); + + /* + * After reset is asserted, the only register that can be read from or + * written to is Control Register 0 (Control Register 0 will read a 01H). + */ + if (m_port[port].control_0 & CR0_RESET) + return m_port[port].control_0; + + u8 data = 0; + switch (m_port[port].reg_pointer) + { + case 0x0: data = control_0_r(port); break; + case 0x1: data = control_1_r(port); break; + case 0x2: data = interrupt_status_r<0>(port); break; + case 0x3: data = interrupt_status_r<1>(port); break; + case 0x4: data = interrupt_status_r<2>(port); break; + case 0x5: data = interrupt_status_r<3>(port); break; + case 0x6: data = interrupt_vector_r(port); break; + case 0x7: data = byte_count_r(port); break; + case 0x8: data = byte_count_comparison_r(port); break; + case 0x9: data = control_2_r(port); break; + case 0xa: data = control_3_r(port); break; + case 0xb: data = message_out_r(port); break; + case 0xc: data = message_in_r(port); break; + case 0xd: data = pattern_match_r(port); break; + case 0xe: data = pattern_mask_r(port); break; + case 0xf: data = fifo_r(port); break; + } + + m_port[port].reg_state = 0; + + LOGMASKED(LOG_REG, "reg_r port %d reg %d data 0x%02x\n", port + 1, m_port[port].reg_pointer, data); + + return data; +} + +void z8038_device::reg_w(u8 const port, u8 data) +{ + // check port 2 enabled + if (port && !(m_control_2 & CR2_P2EN)) + return; + + // FIXME: writes from port 2 in i/o mode + if (port && (m_port[port].control_0 & CR0_P2M_IO)) + fatalerror("unexpected register write from Port 2 in i/o mode\n"); + + /* + * If C/D̅ is 1, the next byte writes into Control Register 0. When in the + * reset state, a write should not be done when C/D̅ is 0. The reset state + * is exited by writing 00H when C/D̅ is 1. + */ + if (m_port[port].control_0 & CR0_RESET) + { + if (data == 0) + { + LOG("reg_w port %d reset state cleared\n", port + 1); + m_port[port].reg_pointer = 0; + m_port[port].reg_state = 0; + + m_port[port].control_0 = data; + } + + return; + } + + if (m_port[port].reg_state == 1) + { + switch (m_port[port].reg_pointer) + { + case 0x0: control_0_w(port, data); break; + case 0x1: control_1_w(port, data); break; + case 0x2: interrupt_status_w<0>(port, data); break; + case 0x3: interrupt_status_w<1>(port, data); break; + case 0x4: interrupt_status_w<2>(port, data); break; + case 0x5: interrupt_status_w<3>(port, data); break; + case 0x6: interrupt_vector_w(port, data); break; + case 0x7: break; // byte count register (read only) + case 0x8: byte_count_comparison_w(port, data); break; + case 0x9: control_2_w(port, data); break; + case 0xa: control_3_w(port, data); break; + case 0xb: message_out_w(port, data); break; + case 0xc: break; // message in register (read only) + case 0xd: pattern_match_w(port, data); break; + case 0xe: pattern_mask_w(port, data); break; + case 0xf: fifo_w(port, data); break; + } + + LOGMASKED(LOG_REG, "reg_w port %d reg %d data 0x%02x\n", port + 1, m_port[port].reg_pointer, data); + + // schedule interrupt check (don't duplicate for fifo) + if (m_port[port].reg_pointer != 0xf) + m_int_check->adjust(attotime::zero); + } + else + m_port[port].reg_pointer = data & 0xf; + + m_port[port].reg_state = !m_port[port].reg_state; +} + +u8 z8038_device::fifo_r(u8 const port) +{ + // check for underflow + if (!m_fifo.empty()) + { + m_port[port].data_buffer = m_fifo.dequeue(); + + fifo_update(); + + LOGMASKED(LOG_FIFO, "fifo_r port %d data 0x%02x\n", port + 1, m_port[port].data_buffer); + } + else + m_port[port].interrupt_status[2] |= (ISR2_UF | ISR2_EIP); + + // schedule interrupt check + m_int_check->adjust(attotime::zero); + + return m_port[port].data_buffer; +} + +void z8038_device::fifo_w(u8 const port, u8 data) +{ + // check for overflow + if (!m_fifo.full()) + { + m_port[port].data_buffer = data; + m_fifo.enqueue(m_port[port].data_buffer); + + fifo_update(); + + LOGMASKED(LOG_FIFO, "fifo_w port %d data 0x%02x\n", port + 1, m_port[port].data_buffer); + } + else + m_port[port].interrupt_status[2] |= (ISR2_OF | ISR2_EIP); + + // schedule interrupt check + m_int_check->adjust(attotime::zero); +} + +u8 z8038_device::control_1_r(u8 const port) +{ + /* + * Bit 5 (Message Register Out Full), if set, indicates that the CPU has + * placed a message in its Message Out register. This bit is reset when the + * receiving CPU reads the message in its Message In register. This bit is + * the other CPU's message IP bit and is a read-only bit. Bit 4 (Message + * Register Interrupt Under Service), if set, indicates that the other CPU + * has received a message in its Message In register. This bit is the + * message IUS (Interrupt Under Service) bit of the other CPU and is a + * read-only bit. + */ + u8 data = m_port[port].control_1; + + if (m_port[!port].interrupt_status[0] & ISR0_MIP) + data |= CR1_MMRF; + + if (m_port[!port].interrupt_status[0] & ISR0_MIUS) + data |= CR1_MMRUS; + + return data; +} + +u8 z8038_device::interrupt_vector_r(u8 const port) +{ + /* + * When MIE is 1, other than during an Interrupt Acknowledge cycle, the + * Interrupt Vector register always reflects the FIO status in these bits, + * regardless of whether or not the Vector Includes Status bit is set. + */ + if (m_port[port].control_0 & CR0_MIE) + return (m_port[port].interrupt_vector & 0xf1) | (m_port[port].int_code << 1); + else + return m_port[port].interrupt_vector; +} + +u8 z8038_device::byte_count_r(u8 const port) +{ + /* + * Bit 6 is reset upon completion of the CPU read of the Byte Count + * register. The ongoing count appears in t he Byte Count register after + * the read. + */ + if (m_port[port].control_1 & CR1_FBCR) + m_port[port].control_1 &= ~CR1_FBCR; + + return m_port[port].byte_count; +} + +u8 z8038_device::control_3_r(u8 const port) +{ + u8 const mask = (port == 0) ? 0xff : 0xf0; + + // return direction relative to controlling port + if (bool(m_control_3 & CR3_P2DIR) != bool(port)) + return (m_control_3 & mask) ^ CR3_DIR; + else + return (m_control_3 & mask); +} + +u8 z8038_device::message_in_r(u8 const port) +{ + /* + * When the Port 2 CPU reads the data from its Message In register, the + * Port 2 IP is cleared. + */ + m_port[port].interrupt_status[0] &= ~ISR0_MIP; + + return m_port[port].message_in; +} + +void z8038_device::control_0_w(u8 const port, u8 data) +{ + if (!(data & CR0_RESET)) + { + if (port == 0) + m_port[port].control_0 = data; + else + m_port[port].control_0 = (m_port[!port].control_0 & CR0_P2M) | (data & ~CR0_P2M); + } + else + port_reset(port); +} + +void z8038_device::control_1_w(u8 const port, u8 data) +{ + m_port[port].control_1 = data & CR1_WMASK; +} + +template void z8038_device::interrupt_status_w(u8 const port, u8 data) +{ + // high interrupt status + switch (data & ISR_HMASK) + { + case 0x20: m_port[port].interrupt_status[Number] &= ~(ISR_HIUS | ISR_HIP); break; + case 0x40: m_port[port].interrupt_status[Number] |= ISR_HIUS; break; + case 0x60: m_port[port].interrupt_status[Number] &= ~ISR_HIUS; break; + case 0x80: m_port[port].interrupt_status[Number] |= ISR_HIP; break; + case 0xa0: m_port[port].interrupt_status[Number] &= ~ISR_HIP; break; + case 0xc0: m_port[port].interrupt_status[Number] |= ISR_HIE; break; + case 0xe0: m_port[port].interrupt_status[Number] &= ~ISR_HIE; break; + } + + // low interrupt status + if (Number != 0) + { + switch (data & ISR_LMASK) + { + case 0x02: m_port[port].interrupt_status[Number] &= ~(ISR_LIUS | ISR_LIP); break; + case 0x04: m_port[port].interrupt_status[Number] |= ISR_LIUS; break; + case 0x06: m_port[port].interrupt_status[Number] &= ~ISR_LIUS; break; + case 0x08: m_port[port].interrupt_status[Number] |= ISR_LIP; break; + case 0x0a: m_port[port].interrupt_status[Number] &= ~ISR_LIP; break; + case 0x0c: m_port[port].interrupt_status[Number] |= ISR_LIE; break; + case 0x0e: m_port[port].interrupt_status[Number] &= ~ISR_LIE; break; + } + } +} + +// instantiate helpers for each interrupt status register +template void z8038_device::interrupt_status_w<0>(u8 const port, u8 data); +template void z8038_device::interrupt_status_w<1>(u8 const port, u8 data); +template void z8038_device::interrupt_status_w<2>(u8 const port, u8 data); +template void z8038_device::interrupt_status_w<3>(u8 const port, u8 data); + +void z8038_device::byte_count_comparison_w(u8 const port, u8 data) +{ + /* + * The largest programmable value is 7Fh (127 decimal). + */ + m_port[port].byte_count_comparison = data & 0x7f; + + // check byte count comparison + if (m_fifo.queue_length() == m_port[port].byte_count_comparison) + m_port[port].interrupt_status[2] |= ISR2_BCCIP; +} + +void z8038_device::control_2_w(u8 const port, u8 data) +{ + if (port == 0) + m_control_2 = data & CR2_WMASK; + else + logerror("cannot write to control register 2 from port 2\n"); +} + +void z8038_device::control_3_w(u8 const port, u8 data) +{ + if (m_port[port].control_0 & CR0_P2M_IO) + { + // update all except unused and input line bits + m_control_3 = data & ~(CR3_UNUSED | CR3_P2IN0); + + // update output lines + m_out_H_cb(m_control_3 & CR3_P2OUT1 ? 1 : 0); + m_out_J_cb(m_control_3 & CR3_P2OUT3 ? 1 : 0); + + // update clear if configured as output + if (!(m_control_3 & CR3_P2CLR)) + { + if (!(m_control_3 & CR3_CLR)) + fifo_clear(); + + m_out_E_cb(m_control_3 & CR3_CLR ? 1 : 0); + } + + // update direction if configured as output + if (!(m_control_3 & CR3_P2DIR)) + m_out_F_cb(m_control_3 & CR3_DIR ? 1 : 0); + + // TODO: resample input lines? + } + else + { + if (port == 0) + { + // flag interrupt pending if in control and changing direction + if (!(data & CR3_P2DIR) && ((data ^ m_control_3) & CR3_DIR)) + m_port[!port].interrupt_status[1] |= ISR1_DDCIP; + + // update clear and direction bits only if in control + u8 const mask = (CR3_P2CLR | CR3_P2DIR | CR3_P2OUT3 | CR3_P2OUT1) + | (data & CR3_P2CLR ? 0 : CR3_CLR) + | (data & CR3_P2DIR ? 0 : CR3_DIR); + + m_control_3 = (m_control_3 & ~mask) | (data & mask); + + // clear fifo + if (!(m_control_3 & (CR3_P2CLR | CR3_CLR))) + fifo_clear(); + } + else + { + // flag interrupt pending if in control and changing direction + if ((data & CR3_P2DIR) && ((data ^ m_control_3) & CR3_DIR)) + m_port[!port].interrupt_status[1] |= ISR1_DDCIP; + + // update clear and direction bits only if in control + u8 const mask = (m_control_3 & (CR3_P2CLR | CR3_P2DIR)) >> 1; + + m_control_3 = (m_control_3 & ~mask) | (data & mask); + + // clear fifo + if ((m_control_3 & CR3_P2CLR) && !(m_control_3 & CR3_CLR)) + fifo_clear(); + } + } +} + +void z8038_device::message_out_w(u8 const port, u8 data) +{ + /* + * When Port 1's CPU writes to the Message Out register which is also Port + * 2's Message In register, Port 2's Message Interrupt Pending bit is set. + */ + m_port[!port].message_in = data; + m_port[!port].interrupt_status[0] |= ISR0_MIP; +} + +WRITE_LINE_MEMBER(z8038_device::in_E) +{ + // check port 2 in i/o mode and pin 35 configured as input + if ((m_port[0].control_0 & CR0_P2M_IO) && (m_control_3 & CR3_P2CLR)) + { + // active low - clear fifo + if (!state) + { + m_control_3 &= ~CR3_CLR; + fifo_clear(); + } + else + m_control_3 |= CR3_CLR; + } +} + +WRITE_LINE_MEMBER(z8038_device::in_F) +{ + // check port 2 in i/o mode and pin 34 configured as input + if ((m_port[0].control_0 & CR0_P2M_IO) && (m_control_3 & CR3_P2DIR)) + { + // check for direction change and flag interrupt + if (bool(state) != bool(m_control_3 & CR3_DIR)) + { + if (state) + m_control_3 |= CR3_DIR; + else + m_control_3 &= ~CR3_DIR; + + // flag interrupt pending + m_port[0].interrupt_status[1] |= ISR1_DDCIP; + + // schedule interrupt check + m_int_check->adjust(attotime::zero); + } + } +} + +WRITE_LINE_MEMBER(z8038_device::in_G) +{ + // check port 2 in i/o mode + if (m_port[0].control_0 & CR0_P2M_IO) + { + if (state) + m_control_3 |= CR3_P2IN0; + else + m_control_3 &= ~CR3_P2IN0; + } +} + +void z8038_device::port_reset(u8 const port) +{ + LOG("port_reset port %d \n", port + 1); + + m_port[port].control_1 = 0; + if (port == 0) + { + m_port[port].control_0 = CR0_RESET; + + m_control_2 = 0; + m_control_3 = 0; + + fifo_clear(); + } + else + { + // port 2 mode is not reset + m_port[port].control_0 = CR0_RESET; + m_port[port].control_0 |= (m_port[!port].control_0 & CR0_P2M); + + /* + * It should be noted that if the Port 2 side is reset when it has + * control of the C̅L̅E̅A̅R̅ bit, the C̅L̅E̅A̅R̅ bit is also reset (0). It should + * be noted that if the Port 2 side is reset when it has control of the + * Data Direction bit, the Data Direction is also reset. + */ + if (m_control_3 & CR3_P2CLR) + { + m_control_3 &= ~CR3_CLR; + fifo_clear(); + } + + if (m_control_3 & CR3_P2DIR) + m_control_3 &= ~CR3_DIR; + } + + m_port[port].pattern_mask = 0; + m_port[port].interrupt_status[0] = 0; + /* + * All bits except D1 and D0 are cleared by reset. Bits D1 and D0 may be a 1 + * or 0 depending on whether a match condition exists or not. + */ + m_port[port].interrupt_status[1] = 0; // opt to ignore "random" pattern matches + m_port[port].interrupt_status[2] = 0; + + /* + * All bits except D0 are cleared by reset. + */ + m_port[port].interrupt_status[3] &= ~ISR3_BE; + + /* + * When Port 1 is reset, Port 2 is also reset. If Port 2 is reset by itself, + * Port 1 is not reset. + */ + if (port == 0) + port_reset(!port); +} + +void z8038_device::fifo_clear() +{ + m_fifo.clear(); + + // FIXME: should clearing the fifo trigger buffer empty interrupts? + m_port[0].interrupt_status[3] |= ISR3_BE; + m_port[0].interrupt_status[3] &= ~ISR3_BF; + m_port[1].interrupt_status[3] |= ISR3_BE; + m_port[1].interrupt_status[3] &= ~ISR3_BF; +} + +void z8038_device::fifo_update() +{ + for (u8 port = 0; port < 2; port++) + { + // update byte count + if (!(m_port[port].control_1 & CR1_FBCR)) + m_port[port].byte_count = m_fifo.queue_length(); + + // pattern match check + if ((m_port[port].data_buffer & ~m_port[port].pattern_mask) == (m_port[port].pattern_match & ~m_port[port].pattern_mask)) + m_port[port].interrupt_status[1] |= (ISR1_PMIP | ISR1_PMF); + else + m_port[port].interrupt_status[1] &= ~ISR1_PMF; + + // byte count comparison check + if (m_fifo.queue_length() == m_port[port].byte_count_comparison) + m_port[port].interrupt_status[2] |= ISR2_BCCIP; + + // buffer full check + // TODO: test full pin (pin 37) + if (m_fifo.full()) + m_port[port].interrupt_status[3] |= (ISR3_BF | ISR3_FIP); + + // buffer empty check + // TODO: test empty pin (pin 37) + if (m_fifo.empty()) + m_port[port].interrupt_status[3] |= (ISR3_BE | ISR3_EIP); + } +} + +TIMER_CALLBACK_MEMBER(z8038_device::int_check) +{ + for (u8 port = 0; port < 2; port++) + { + // check master interrupt enable + if (!(m_port[port].control_0 & CR0_MIE)) + { + set_int_state(port, false); + continue; + } + + // check any interrupts under service + if (std::any_of( + std::begin(m_port[port].interrupt_status), + std::end(m_port[port].interrupt_status), + [](u8 const val) { return bool(val & (ISR_HIUS | ISR_LIUS)); })) + continue; + + // check for enabled and pending interrupts in priority order + m_port[port].int_code = 7; + for (u8 &isr : m_port[port].interrupt_status) + { + // check high interrupt enable and pending + if ((isr & ISR_HIE) && (isr & ISR_HIP)) + { + // set interrupt under service + isr |= ISR_HIUS; + break; + } + m_port[port].int_code--; + + if (m_port[port].int_code != 6) + { + // check low interrupt enable and pending + if ((isr & ISR_LIE) && (isr & ISR_LIP)) + { + // set interrupt under service + isr |= ISR_LIUS; + break; + } + m_port[port].int_code--; + } + } + + if (m_port[port].int_code) + LOGMASKED(LOG_INT, "int_check port %d interrupt code %d detected\n", port + 1, m_port[port].int_code); + + // update interrupt state + set_int_state(port, bool(m_port[port].int_code)); + } +} + +void z8038_device::set_int_state(u8 const port, bool asserted) +{ + if (m_port[port].int_asserted != asserted) + { + LOGMASKED(LOG_INT, "set_int_state port %d interrupt %s\n", + port + 1, asserted ? "asserted" : "deasserted"); + + m_port[port].int_asserted = asserted; + + // line is active low + m_out_int_cb[port](asserted ? 0 : 1); + } +} diff --git a/src/devices/machine/z8038.h b/src/devices/machine/z8038.h new file mode 100644 index 00000000000..d3e1532c40d --- /dev/null +++ b/src/devices/machine/z8038.h @@ -0,0 +1,229 @@ +// license:BSD-3-Clause +// copyright-holders:Patrick Mackinlay + +#ifndef MAME_MACHINE_Z8038_H +#define MAME_MACHINE_Z8038_H + +#pragma once + +class z8038_device : public device_t +{ +public: + z8038_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock); + + // port 1 and 2 I̅N̅T̅ output lines + template auto out_int_cb() { return m_out_int_cb[Port - 1].bind(); } + + // port 2 output lines + auto out_E() { return m_out_E_cb.bind(); } + auto out_F() { return m_out_F_cb.bind(); } + auto out_H() { return m_out_H_cb.bind(); } + auto out_J() { return m_out_J_cb.bind(); } + + // port 2 input lines + DECLARE_WRITE_LINE_MEMBER(in_E); // C̅L̅E̅A̅R̅ + DECLARE_WRITE_LINE_MEMBER(in_F); // Data Direction + DECLARE_WRITE_LINE_MEMBER(in_G); // IN0 + + // indirect register access + template u8 reg_r() { return reg_r(Port - 1); } + template void reg_w(u8 data) { reg_w(Port - 1, data); } + + // direct register access + template void zbus_map(address_map &map); + template u8 zbus_reg_r(offs_t offset) { m_port[Port - 1].reg_state = 1; m_port[Port - 1].reg_pointer = offset & 0xf; return reg_r(Port - 1); } + template void zbus_reg_w(offs_t offset, u8 data) { m_port[Port - 1].reg_state = 1; m_port[Port - 1].reg_pointer = offset & 0xf; reg_w(Port - 1, data); } + + // direct fifo access + template u8 fifo_r() { return fifo_r(Port - 1); } + template void fifo_w(u8 data) { fifo_w(Port - 1, data); } + +protected: + // standard device_interface overrides + virtual void device_start() override; + virtual void device_reset() override; + + // primary device read/write handlers + u8 reg_r(u8 const port); + void reg_w(u8 const port, u8 data); + u8 fifo_r(u8 const port); + void fifo_w(u8 const port, u8 data); + + // register read helpers + u8 control_0_r(u8 const port) { return m_port[port].control_0; } + u8 control_1_r(u8 const port); + template u8 interrupt_status_r(u8 const port) { return m_port[port].interrupt_status[Number]; } + u8 interrupt_vector_r(u8 const port); + u8 byte_count_r(u8 const port); + u8 byte_count_comparison_r(u8 const port) { return m_port[port].byte_count_comparison; } + u8 control_2_r(u8 const port) { return (port == 0) ? m_control_2 : 0; } + u8 control_3_r(u8 const port); + u8 message_out_r(u8 const port) { return m_port[!port].message_in; } + u8 message_in_r(u8 const port); + u8 pattern_match_r(u8 const port) { return m_port[port].pattern_match; } + u8 pattern_mask_r(u8 const port) { return m_port[port].pattern_mask; } + + // register write helpers + void control_0_w(u8 const port, u8 data); + void control_1_w(u8 const port, u8 data); + template void interrupt_status_w(u8 const port, u8 data); + void interrupt_vector_w(u8 const port, u8 data) { m_port[port].interrupt_vector = data; } + void byte_count_comparison_w(u8 const port, u8 data); + void control_2_w(u8 const port, u8 data); + void control_3_w(u8 const port, u8 data); + void message_out_w(u8 const port, u8 data); + void pattern_match_w(u8 const port, u8 data) { m_port[port].pattern_match = data; } + void pattern_mask_w(u8 const port, u8 data) { m_port[port].pattern_mask = data; } + + // other helpers + void port_reset(u8 const port); + void fifo_clear(); + void fifo_update(); + TIMER_CALLBACK_MEMBER(int_check); + void set_int_state(u8 const port, bool asserted); + +private: + enum control_0_mask : u8 + { + CR0_RESET = 0x01, // reset port + CR0_RJA = 0x02, // right-justify address + CR0_P2M = 0x0c, // port 2 mode (read-only from port 2 side) + CR0_VIS = 0x10, // vector includes status + CR0_NV = 0x20, // no vector on interrupt + CR0_DLC = 0x40, // disable lower daisy chain + CR0_MIE = 0x80, // master interrupt enable + }; + enum control_0_p2mode_mask : u8 + { + CR0_P2M_ZBUSCPU = 0x00, // z-bus cpu + CR0_P2M_NZBUSCPU = 0x04, // non z-bus cpu + CR0_P2M_3WHSIO = 0x08, // 3-wire handshake i/o + CR0_P2M_2WHSIO = 0x0c, // 2-wire handshake i/o + + CR0_P2M_IO = 0x08, // i/o mode + }; + enum control_1_mask : u8 + { + CR1_RWE = 0x01, // R̅E̅Q̅U̅E̅S̅T̅ or W̅A̅I̅T̅ enable + CR1_RWS = 0x02, // R̅E̅Q̅U̅E̅S̅T̅ or W̅A̅I̅T̅ select + CR1_SDBC = 0x04, // start dma on byte count + CR1_SDPM = 0x08, // stop dma on pattern match + CR1_MMRUS = 0x10, // message mailbox register under service (read only) + CR1_MMRF = 0x20, // message mailbox register full (read only) + CR1_FBCR = 0x40, // freeze byte count register + + CR1_WMASK = 0x4f, + }; + enum control_2_mask : u8 + { + CR2_P2EN = 0x01, // port 2 side enable + CR2_P2HE = 0x02, // port 2 side handshake enable + + CR2_WMASK = 0x03, + }; + enum control_3_mask : u8 + { + CR3_P2IN0 = 0x01, // port 2 input line 0 + CR3_P2OUT1 = 0x02, // port 2 output line 1 + CR3_UNUSED = 0x04, // not used (mut be programmed 0) + CR3_P2OUT3 = 0x08, // port 2 output line 3 + CR3_DIR = 0x10, // data direction + CR3_P2DIR = 0x20, // port 2 controls direction + CR3_CLR = 0x40, // clear fifo (active low) + CR3_P2CLR = 0x80, // port 2 controls clear + }; + enum interrupt_status_0_mask : u8 + { + ISR0_MIP = 0x20, // message interrupt pending + ISR0_MIE = 0x40, // message interrupt enable + ISR0_MIUS = 0x80, // message interrupt under service + + ISR0_WMASK = 0xe0 + }; + enum interrupt_status_1_mask : u8 + { + ISR1_PMF = 0x01, // pattern match flag + ISR1_PMIP = 0x02, // pattern match interrupt pending + ISR1_PMIE = 0x04, // pattern match interrupt enable + ISR1_PMIUS = 0x08, // pattern match interrupt under service + ISR1_DDCIP = 0x20, // data direction change interrupt pending + ISR1_DDCIE = 0x40, // data direction change interrupt enable + ISR1_DDCIUS = 0x80, // data direction change interrupt under service + }; + enum interrupt_status_2_mask : u8 + { + ISR2_UF = 0x01, // underflow error + ISR2_EIP = 0x02, // error interrupt pending + ISR2_EIE = 0x04, // error interrupt enable + ISR2_EIUS = 0x08, // error interrupt under service + ISR2_OF = 0x10, // overflow error + ISR2_BCCIP = 0x20, // byte count compare interrupt pending + ISR2_BCCIE = 0x40, // byte count compare interrupt enable + ISR2_BCCIUS = 0x80, // byte count compare interrupt under service + }; + enum interrupt_status_3_mask : u8 + { + ISR3_BE = 0x01, // buffer empty + ISR3_EIP = 0x02, // empty interrupt pending + ISR3_EIE = 0x04, // empty interrupt enable + ISR3_EIUS = 0x08, // empty interrupt under service + ISR3_BF = 0x10, // buffer full + ISR3_FIP = 0x20, // full interrupt pending + ISR3_FIE = 0x40, // full interrupt enable + ISR3_FIUS = 0x80, // full interrupt under service + }; + + // generic interrupt status bit names + enum interrupt_status_mask : u8 + { + ISR_LIP = 0x02, // low interrupt pending + ISR_LIE = 0x04, // low interrupt enable + ISR_LIUS = 0x08, // low interrupt under service + ISR_HIP = 0x20, // high interrupt pending + ISR_HIE = 0x40, // high interrupt enable + ISR_HIUS = 0x80, // high interrupt under service + + ISR_LMASK = 0x0e, + ISR_HMASK = 0xe0, + }; + + devcb_write_line m_out_int_cb[2]; + devcb_write_line m_out_E_cb; // pin number 35 + devcb_write_line m_out_F_cb; // pin number 34 + devcb_write_line m_out_H_cb; // pin number 32 + devcb_write_line m_out_J_cb; // pin number 30 + + emu_timer *m_int_check; + + // registers largely or entirely controlled by port 1 + u8 m_control_2; + u8 m_control_3; + + struct port_state + { + // non-register, per-port state + u8 reg_state; + u8 reg_pointer; + u8 int_code; + bool int_asserted; + + // accessible registers + u8 control_0; + u8 control_1; + u8 interrupt_status[4]; + u8 interrupt_vector; + u8 byte_count; + u8 byte_count_comparison; + u8 message_in; + u8 pattern_match; + u8 pattern_mask; + u8 data_buffer; + } + m_port[2]; + + util::fifo m_fifo; +}; + +DECLARE_DEVICE_TYPE(Z8038, z8038_device) + +#endif // MAME_MACHINE_Z8038_H -- cgit v1.2.3