// license:BSD-3-Clause
// copyright-holders:Ariane Fugmann
/*
Comm PCB
--------
MODEL-1 COMMUNICATION BD 837-8842 171-6293B (C) SEGA 1992
|--------------------------------------------------------------------------------|
| |
| MB89237A MB89374 |
| JP4 LED1 |
| 15112.17 Z80 |
| JP2 JP3 75179 |
| MB8464 315-5624 JP6 |
| 315-5547 |
| 315-5611 SW1 PC910 CN4|
| |
| PC910 CN5|
| MB8421 MB8431 JP7 |
| JP5 |
| JP8 CN7|
| CN1 CN2 |
| |---------------------------------| |---------------------------------| CN6|
| |---------------------------------| |---------------------------------| |
|--------------------------------------------------------------------------------|
Notes:
15112.17 - AMD AM27C100 128k x8 EPROM (DIP32, labelled 'EPR-15112')
Z80 - Zilog Z0840008PSC Z80 CPU, running at 8.000MHz (DIP40)
MB8464 - Fujitsu MB8464 8k x8 SRAM (DIP28)
MB8421 - Fujitsu MB8421-12LP 2k x8 SRAM (SDIP52)
MB8431 - Fujitsu MB8431-90LP 2k x8 SRAM (SDIP52)
MB89237A - Fujitsu MB89237A DMA-Controller (DIP20) [most likely i8237A clone]
MB89374 - Fujitsu MB89374 Data Link Controller (SDIP42)
75179 - Texas Instruments SN75179 Differential Driver and Receiver Pair (DIP8)
315-5547 - AMI 18CV8PC-25 PAL (DIP20)
315-5624 - MMI PAL16L8BCN PAL (DIP20)
315-5611 - Lattice GAL16V8A PAL (DIP20)
PC910 - Sharp PC910 opto-isolator (x2, DIP8)
SW1 - Push Button Switch (enables board)
CN1, CN2 - Connectors to join Comm board to Video board
CN4 - 8 pin connector (DIFFERENTIAL port)
CN5 - 6 pin connector (SERIAL port)
CN6, CN7 - TOSLINK-Connectors for network optical cable link
JP2 - Jumper, set to 2-3 (connected to EPROM A15)
JP3 - Jumper, set to 1-2 (connected to EPROM A16)
JP4 - Jumper, set to 1-2
JP5 - Jumper, shorted (enables TOSLINK RX channel)
JP6 - Jumper, not shorted (enables DIFFERERENTIAL RX channel)
JP7 - Jumper, not shorted (enables SERIAL RX channel)
JP8 - Jumper, set to 1-2 (selects CLOCK SOURCE)
*/
#include "emu.h"
#include "machine/m1comm.h"
#include "emuopts.h"
#define Z80_TAG "commcpu"
/*************************************
* M1COMM Memory Map
*************************************/
void m1comm_device::m1comm_mem(address_map &map)
{
map(0x0000, 0x7fff).rom();
map(0x8000, 0x9fff).ram();
map(0xc000, 0xffff).mask(0x0fff).rw(FUNC(m1comm_device::share_r), FUNC(m1comm_device::share_w));
}
/*************************************
* M1COMM I/O Map
*************************************/
void m1comm_device::m1comm_io(address_map &map)
{
map.global_mask(0x7f);
map(0x00, 0x1f).rw(m_dlc, FUNC(mb89374_device::read), FUNC(mb89374_device::write));
map(0x20, 0x2f).rw(m_dma, FUNC(am9517a_device::read), FUNC(am9517a_device::write));
map(0x40, 0x5f).mask(0x01).rw(FUNC(m1comm_device::syn_r), FUNC(m1comm_device::syn_w));
map(0x60, 0x7f).mask(0x01).rw(FUNC(m1comm_device::zfg_r), FUNC(m1comm_device::zfg_w));
}
ROM_START( m1comm )
ROM_REGION( 0x20000, Z80_TAG, ROMREGION_ERASEFF )
ROM_DEFAULT_BIOS("epr15112")
// found on Virtua Racing and WingWar
ROM_SYSTEM_BIOS( 0, "epr15112", "EPR-15112" )
ROMX_LOAD( "epr-15112.17", 0x0000, 0x20000, CRC(4950e771) SHA1(99014124e0324dd114cb22f55159d18b597a155a), ROM_BIOS(0) )
// found on Virtua Formula
ROM_SYSTEM_BIOS( 1, "epr15624", "EPR-15624" )
ROMX_LOAD( "epr-15624.17", 0x0000, 0x20000, CRC(9b3ba315) SHA1(0cd0983cc8b2f2d6b41617d0d0a24cc6c188e62a), ROM_BIOS(1) )
ROM_END
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
DEFINE_DEVICE_TYPE(M1COMM, m1comm_device, "m1comm", "Model-1 Communication Board")
//-------------------------------------------------
// device_add_mconfig - add device configuration
//-------------------------------------------------
void m1comm_device::device_add_mconfig(machine_config &config)
{
Z80(config, m_cpu, 8000000); // 32 MHz / 4
m_cpu->set_memory_map(&m1comm_device::m1comm_mem);
m_cpu->set_io_map(&m1comm_device::m1comm_io);
AM9517A(config, m_dma, 8000000); // 32 MHz / 4
m_dma->out_hreq_callback().set(FUNC(m1comm_device::dma_hreq_w));
m_dma->in_memr_callback().set(FUNC(m1comm_device::dma_mem_r));
m_dma->out_memw_callback().set(FUNC(m1comm_device::dma_mem_w));
m_dma->out_dack_callback<2>().set(m_dlc, FUNC(mb89374_device::pi3_w));
m_dma->out_dack_callback<3>().set(m_dlc, FUNC(mb89374_device::pi2_w));
m_dma->out_eop_callback().set(m_dlc, FUNC(mb89374_device::ci_w));
m_dma->in_ior_callback<2>().set(m_dlc, FUNC(mb89374_device::dma_r));
m_dma->out_iow_callback<3>().set(m_dlc, FUNC(mb89374_device::dma_w));
MB89374(config, m_dlc, 8000000); // 32 MHz / 4
m_dlc->out_po_callback<2>().set(m_dma, FUNC(am9517a_device::dreq3_w));
m_dlc->out_po_callback<3>().set(m_dma, FUNC(am9517a_device::dreq2_w));
m_dlc->out_irq_callback().set(FUNC(m1comm_device::dlc_int7_w));
}
//-------------------------------------------------
// rom_region - device-specific ROM region
//-------------------------------------------------
const tiny_rom_entry *m1comm_device::device_rom_region() const
{
return ROM_NAME( m1comm );
}
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// m1comm_device - constructor
//-------------------------------------------------
m1comm_device::m1comm_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock) :
device_t(mconfig, M1COMM, tag, owner, clock),
m_cpu(*this, Z80_TAG),
m_dma(*this, "commdma"),
m_dlc(*this, "commdlc")
{
#ifdef M1COMM_SIMULATION
// prepare localhost "filename"
m_localhost[0] = 0;
strcat(m_localhost, "socket.");
strcat(m_localhost, mconfig.options().comm_localhost());
strcat(m_localhost, ":");
strcat(m_localhost, mconfig.options().comm_localport());
// prepare remotehost "filename"
m_remotehost[0] = 0;
strcat(m_remotehost, "socket.");
strcat(m_remotehost, mconfig.options().comm_remotehost());
strcat(m_remotehost, ":");
strcat(m_remotehost, mconfig.options().comm_remoteport());
m_framesync = mconfig.options().comm_framesync() ? 0x01 : 0x00;
#endif
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void m1comm_device::device_start()
{
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void m1comm_device::device_reset()
{
m_syn = 0;
m_zfg = 0;
m_cn = 0;
m_fg = 0;
}
void m1comm_device::device_reset_after_children()
{
m_cpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
m_dma->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
m_dlc->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
}
WRITE_LINE_MEMBER(m1comm_device::dma_hreq_w)
{
m_cpu->set_input_line(INPUT_LINE_HALT, state ? ASSERT_LINE : CLEAR_LINE);
m_dma->hack_w(state);
}
uint8_t m1comm_device::dma_mem_r(offs_t offset)
{
return m_cpu->space(AS_PROGRAM).read_byte(offset);
}
void m1comm_device::dma_mem_w(offs_t offset, uint8_t data)
{
m_cpu->space(AS_PROGRAM).write_byte(offset, data);
}
WRITE_LINE_MEMBER(m1comm_device::dlc_int7_w)
{
m_cpu->set_input_line_and_vector(0, state ? ASSERT_LINE : CLEAR_LINE, 0xff); // Z80
}
uint8_t m1comm_device::syn_r()
{
return m_syn | 0xfc;
}
void m1comm_device::syn_w(uint8_t data)
{
m_syn = data & 0x03;
}
uint8_t m1comm_device::zfg_r()
{
return m_zfg | (~m_fg << 7) | 0x7e;
}
void m1comm_device::zfg_w(uint8_t data)
{
m_zfg = data & 0x01;
}
uint8_t m1comm_device::share_r(offs_t offset)
{
return m_shared[offset];
}
void m1comm_device::share_w(offs_t offset, uint8_t data)
{
m_shared[offset] = data;
}
uint8_t m1comm_device::cn_r()
{
return m_cn | 0xfe;
}
void m1comm_device::cn_w(uint8_t data)
{
m_cn = data & 0x01;
#ifndef M1COMM_SIMULATION
if (!m_cn)
{
device_reset();
device_reset_after_children();
}
else
{
m_cpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
m_dma->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
m_dlc->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
}
#else
if (!m_cn)
{
// reset command
osd_printf_verbose("M1COMM: board disabled\n");
m_linkenable = 0x00;
m_zfg = 0x00;
}
else
{
// init command
osd_printf_verbose("M1COMM: board enabled\n");
m_linkenable = 0x01;
m_linkid = 0x00;
m_linkalive = 0x00;
m_linkcount = 0x00;
m_linktimer = 0x00e8; // 58 fps * 4s
}
#endif
}
uint8_t m1comm_device::fg_r()
{
return m_fg | (~m_zfg << 7) | 0x7e;
}
void m1comm_device::fg_w(uint8_t data)
{
if (!m_cn)
return;
m_fg = data & 0x01;
}
void m1comm_device::check_vint_irq()
{
#ifndef M1COMM_SIMULATION
if (m_syn & 0x02)
{
m_cpu->set_input_line_and_vector(0, HOLD_LINE, 0xef); // Z80
}
#else
comm_tick();
#endif
}
#ifdef M1COMM_SIMULATION
void m1comm_device::comm_tick()
{
if (m_linkenable == 0x01)
{
int frameStart = 0x0010;
int frameOffset = 0x0000;
int frameSize = 0x01c4;
int dataSize = frameSize + 1;
int recv = 0;
int idx = 0;
bool isMaster = (m_shared[1] == 0x01);
bool isSlave = (m_shared[1] == 0x02);
bool isRelay = (m_shared[1] == 0x00);
if (m_linkalive == 0x02)
{
// link failed...
m_shared[0] = 0xff;
return;
}
else if (m_linkalive == 0x00)
{
// link not yet established...
m_shared[0] = 0x05;
// check rx socket
if (!m_line_rx)
{
osd_printf_verbose("M1COMM: listen on %s\n", m_localhost);
uint64_t filesize; // unused
osd_file::open(m_localhost, OPEN_FLAG_CREATE, m_line_rx, filesize);
}
// check tx socket
if (!m_line_tx)
{
osd_printf_verbose("M1COMM: connect to %s\n", m_remotehost);
uint64_t filesize; // unused
osd_file::open(m_remotehost, 0, m_line_tx, filesize);
}
// if both sockets are there check ring
if (m_line_rx && m_line_tx)
{
// try to read one message
recv = read_frame(dataSize);
while (recv > 0)
{
// check message id
idx = m_buffer0[0];
// 0xFF - link id
if (idx == 0xff)
{
if (isMaster)
{
// master gets first id and starts next state
m_linkid = 0x01;
m_linkcount = m_buffer0[1];
m_linktimer = 0x00;
}
else
{
// slave get own id, relay does nothing
if (isSlave)
{
m_buffer0[1]++;
m_linkid = m_buffer0[1];
}
// forward message to other nodes
send_frame(dataSize);
}
}
// 0xFE - link size
else if (idx == 0xfe)
{
if (isSlave || isRelay)
{
m_linkcount = m_buffer0[1];
// forward message to other nodes
send_frame(dataSize);
}
// consider it done
osd_printf_verbose("M1COMM: link established - id %02x of %02x\n", m_linkid, m_linkcount);
m_linkalive = 0x01;
m_zfg = 0x01;
// write to shared mem
m_shared[0] = 0x01;
m_shared[2] = m_linkid;
m_shared[3] = m_linkcount;
}
if (m_linkalive == 0x00)
recv = read_frame(dataSize);
else
recv = 0;
}
// if we are master and link is not yet established
if (isMaster && (m_linkalive == 0x00))
{
// send first packet
if (m_linktimer == 0x01)
{
m_buffer0[0] = 0xff;
m_buffer0[1] = 0x01;
send_frame(dataSize);
}
// send second packet
else if (m_linktimer == 0x00)
{
m_buffer0[0] = 0xfe;
m_buffer0[1] = m_linkcount;
send_frame(dataSize);
// consider it done
osd_printf_verbose("M1COMM: link established - id %02x of %02x\n", m_linkid, m_linkcount);
m_linkalive = 0x01;
m_zfg = 0x01;
// write to shared mem
m_shared[0] = 0x01;
m_shared[2] = m_linkid;
m_shared[3] = m_linkcount;
}
else if (m_linktimer > 0x01)
{
// decrease delay timer
m_linktimer--;
}
}
}
}
if (m_linkalive == 0x01)
{
// link established
do
{
// try to read a message
recv = read_frame(dataSize);
while (recv > 0)
{
// check if valid id
idx = m_buffer0[0];
if (idx > 0 && idx <= m_linkcount)
{
// if not own message
if (idx != m_linkid)
{
// save message to "ring buffer"
frameOffset = frameStart + (idx * frameSize);
for (int j = 0x00 ; j < frameSize ; j++)
{
m_shared[frameOffset + j] = m_buffer0[1 + j];
}
// forward message to other nodes
send_frame(dataSize);
}
}
else
{
if (idx == 0xfc)
{
// 0xFC - VSYNC
m_linktimer = 0x00;
if (!isMaster)
// forward message to other nodes
send_frame(dataSize);
}
if (idx == 0xfd)
{
// 0xFD - master addional bytes
if (!isMaster)
{
// save message to "ring buffer"
frameOffset = 0x06;
for (int j = 0x00 ; j < 0x0a ; j++)
{
m_shared[frameOffset + j] = m_buffer0[1 + j];
}
// forward message to other nodes
send_frame(dataSize);
}
}
}
// try to read another message
recv = read_frame(dataSize);
}
}
while (m_linktimer == 0x01);
// enable wait for vsync
m_linktimer = m_framesync;
// update "ring buffer" if link established
// live relay does not send data
if (m_linkid != 0x00)
{
// check ready-to-send flag
if (m_shared[4] != 0x00)
{
send_data(m_linkid, frameStart, frameSize, dataSize);
// save message to "ring buffer"
frameOffset = frameStart + (m_linkid * frameSize);
for (int j = 0x00 ; j < frameSize ; j++)
{
m_shared[frameOffset + j] = m_buffer0[1 + j];
}
}
if (isMaster)
{
// master sends additional status bytes
send_data(0xfd, 0x06, 0x0a, dataSize);
// send vsync
m_buffer0[0] = 0xfc;
m_buffer0[1] = 0x01;
send_frame(dataSize);
}
}
// clear 05
m_shared[5] = 0x00;
}
}
}
int m1comm_device::read_frame(int dataSize)
{
if (!m_line_rx)
return 0;
// try to read a message
std::uint32_t recv = 0;
std::error_condition filerr = m_line_rx->read(m_buffer0, 0, dataSize, recv);
if (recv > 0)
{
// check if message complete
if (recv != dataSize)
{
// only part of a message - read on
std::uint32_t togo = dataSize - recv;
int offset = recv;
while (togo > 0)
{
filerr = m_line_rx->read(m_buffer1, 0, togo, recv);
if (recv > 0)
{
for (int i = 0 ; i < recv ; i++)
{
m_buffer0[offset + i] = m_buffer1[i];
}
togo -= recv;
offset += recv;
}
else if (!filerr && recv == 0)
{
togo = 0;
}
}
}
}
else if (!filerr && recv == 0)
{
if (m_linkalive == 0x01)
{
osd_printf_verbose("M1COMM: rx connection lost\n");
m_linkalive = 0x02;
m_linktimer = 0x00;
m_shared[0] = 0xff;
m_line_rx.reset();
m_line_tx.reset();
}
}
return recv;
}
void m1comm_device::send_data(uint8_t frameType, int frameStart, int frameSize, int dataSize)
{
m_buffer0[0] = frameType;
for (int i = 0x00 ; i < frameSize ; i++)
{
m_buffer0[1 + i] = m_shared[frameStart + i];
}
send_frame(dataSize);
}
void m1comm_device::send_frame(int dataSize){
if (!m_line_tx)
return;
std::error_condition filerr;
std::uint32_t written;
filerr = m_line_tx->write(&m_buffer0, 0, dataSize, written);
if (filerr)
{
if (m_linkalive == 0x01)
{
osd_printf_verbose("M1COMM: tx connection lost\n");
m_linkalive = 0x02;
m_linktimer = 0x00;
m_shared[0] = 0xff;
m_line_rx.reset();
m_line_tx.reset();
}
}
}
#endif