mirror of
https://github.com/holub/mame
synced 2025-04-24 01:11:11 +03:00
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.
This commit is contained in:
parent
ece4404b69
commit
cea5bbace4
@ -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
|
||||
|
@ -671,6 +671,7 @@ MACHINES["IOPDMA"] = true
|
||||
MACHINES["IOPINTC"] = true
|
||||
MACHINES["IOPSIO2"] = true
|
||||
MACHINES["IOPTIMER"] = true
|
||||
MACHINES["Z8038"] = true
|
||||
|
||||
--------------------------------------------------
|
||||
-- specify available bus cores
|
||||
|
700
src/devices/machine/z8038.cpp
Normal file
700
src/devices/machine/z8038.cpp
Normal file
@ -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 <u8 Port> void z8038_device::zbus_map(address_map &map)
|
||||
{
|
||||
map(0x0, 0xf).rw(FUNC(z8038_device::zbus_reg_r<Port>), FUNC(z8038_device::zbus_reg_w<Port>));
|
||||
|
||||
// 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 <u8 Number> 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);
|
||||
}
|
||||
}
|
229
src/devices/machine/z8038.h
Normal file
229
src/devices/machine/z8038.h
Normal file
@ -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 <u8 Port> 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 Port> u8 reg_r() { return reg_r(Port - 1); }
|
||||
template <u8 Port> void reg_w(u8 data) { reg_w(Port - 1, data); }
|
||||
|
||||
// direct register access
|
||||
template <u8 Port> void zbus_map(address_map &map);
|
||||
template <u8 Port> 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 <u8 Port> 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 Port> u8 fifo_r() { return fifo_r(Port - 1); }
|
||||
template <u8 Port> 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 Number> 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 <u8 Number> 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 <u8, 128> m_fifo;
|
||||
};
|
||||
|
||||
DECLARE_DEVICE_TYPE(Z8038, z8038_device)
|
||||
|
||||
#endif // MAME_MACHINE_Z8038_H
|
Loading…
Reference in New Issue
Block a user