// license:BSD-3-Clause
// copyright-holders:Chris Moore, Nicola Salmoria
/***************************************************************************
bublbobl.cpp
Functions to emulate general aspects of the machine (RAM, ROM, interrupts,
I/O ports)
***************************************************************************/
#include "emu.h"
#include "includes/bublbobl.h"
#include "cpu/z80/z80.h"
void bublbobl_state::common_sreset(int state)
{
if ((state != CLEAR_LINE) && !m_sreset_old)
{
if (m_ym2203 != nullptr) m_ym2203->reset(); // ym2203, if present, is reset
if (m_ym3526 != nullptr) m_ym3526->reset(); // ym3526, if present, is reset
m_audiocpu->set_input_line(INPUT_LINE_IRQ0, CLEAR_LINE); // if a sound irq is active, it is cleared. is this necessary? if the above two devices de-assert /IRQ on reset (as a device_line write) properly, it shouldn't be...
m_sound_to_main->acknowledge_w(m_audiocpu->space(AS_PROGRAM), 0, 0x00, 0xFF); // sound->main semaphore is cleared
m_soundnmi->in_w<0>(0); // sound nmi enable is unset
}
m_audiocpu->set_input_line(INPUT_LINE_RESET, state); // soundcpu is reset
m_sreset_old = (ASSERT_LINE == state);
}
/* bublbobl bankswitch reg
76543210
|||||\\\- Select ROM bank
||||\---- N.C.
|||\----- /SBRES [SUBCPU /RESET]
||\------ /SEQRES [MCU /RESET]
|\------- /BLACK [Video Enable]
\-------- VHINV [flip screen]
// 44 74 74 76 or 76 36 76 once or more per frame...
*/
WRITE8_MEMBER(bublbobl_state::bublbobl_bankswitch_w)
{
//logerror("bankswitch_w: write of %02X\n", data);
/* bits 0-2 select ROM bank */
membank("bank1")->set_entry((data ^ 4) & 7);
/* bit 3 n.c. */
/* bit 4 resets subcpu Z80 */
m_subcpu->set_input_line(INPUT_LINE_RESET, (data & 0x10) ? CLEAR_LINE : ASSERT_LINE);
/* bit 5 resets mcu */
if (m_mcu != nullptr) // only if we have a MCU
m_mcu->set_input_line(INPUT_LINE_RESET, (data & 0x20) ? CLEAR_LINE : ASSERT_LINE);
/* bit 6 enables display */
m_video_enable = data & 0x40;
/* bit 7 flips screen */
flip_screen_set(data & 0x80);
}
/* tokio bankswitch reg
76543210
|||||\\\- Select ROM bank
||||\---- ? used (idle high, /SEQRES?)
|||\----- not used?
||\------ not used?
|\------- ? used (idle high, /BLACK?)
\-------- ? used (idle high, /SRESET?)
// bublboblp: test and main: 00 C8 C9 C8 C9...; tokio: test 00 09 09 49 main 00 09 C8 CF
*/
WRITE8_MEMBER(bublbobl_state::tokio_bankswitch_w)
{
/* bits 0-2 select ROM bank */
membank("bank1")->set_entry(data & 7);
/* bit 3 unknown */
/* GUESS: bit 3 resets mcu */
if (m_mcu != nullptr) // only if we have a MCU
m_mcu->set_input_line(INPUT_LINE_RESET, (data & 0x08) ? CLEAR_LINE : ASSERT_LINE);
/* bit 4 and 5 unknown, not used? */
/* bit 6 is unknown */
/* GUESS: bit 6 is video enable "/BLACK" */
m_video_enable = data & 0x40; // guess
/* bit 7 is unknown but used */
}
/* tokio videoctrl reg
76543210
||||\\\\- not used?
|||\----- OUT (coin lockout to pc030cm, active low)
||\------ ? used (idle low, maybe 2WAY to pc030cm?)
|\------- ? used (idle high, /SBRES? or /SBINT?)
\-------- VHINV (flip screen)
*/
WRITE8_MEMBER(bublbobl_state::tokio_videoctrl_w)
{
//logerror("tokio_videoctrl_w: write of %02X\n", data);
/* bits 0-3 not used? */
/* bit 4 is the coin lockout */
machine().bookkeeping().coin_lockout_global_w(~data & 0x10);
/* bit 5 and 6 are unknown but used */
/* bit 7 flips screen */
flip_screen_set(data & 0x80);
}
WRITE8_MEMBER(bublbobl_state::bublbobl_nmitrigger_w)
{
m_subcpu->pulse_input_line(INPUT_LINE_NMI, attotime::zero);
}
READ8_MEMBER(bublbobl_state::tokiob_mcu_r)
{
/* This return value is literally set by a resistor on the bootleg tokio pcb;
the MCU footprint is unpopulated but for a resistor tying what would be the
PA6 pin to ground. The remaining pins seem to float high. */
return 0xbf;
}
void bublbobl_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
{
switch (id)
{
case TIMER_M68705_IRQ_ACK:
m_mcu->set_input_line(0, CLEAR_LINE);
break;
default:
assert_always(false, "Unknown id in bublbobl_state::device_timer");
}
}
WRITE8_MEMBER(bublbobl_state::bublbobl_soundcpu_reset_w)
{
//logerror("soundcpu_reset_w called with data of %d\n", data);
common_sreset(data ? ASSERT_LINE : CLEAR_LINE);
}
READ8_MEMBER(bublbobl_state::common_sound_semaphores_r)
{
uint8_t ret = 0xfc;
ret |= m_main_to_sound->pending_r() ? 0x2 : 0x0;
ret |= m_sound_to_main->pending_r() ? 0x1 : 0x0;
return ret;
}
/***************************************************************************
Bubble Bobble MCU
***************************************************************************/
READ8_MEMBER(bublbobl_state::bublbobl_mcu_ddr1_r)
{
return m_ddr1;
}
WRITE8_MEMBER(bublbobl_state::bublbobl_mcu_ddr1_w)
{
m_ddr1 = data;
}
READ8_MEMBER(bublbobl_state::bublbobl_mcu_ddr2_r)
{
return m_ddr2;
}
WRITE8_MEMBER(bublbobl_state::bublbobl_mcu_ddr2_w)
{
m_ddr2 = data;
}
READ8_MEMBER(bublbobl_state::bublbobl_mcu_ddr3_r)
{
return m_ddr3;
}
WRITE8_MEMBER(bublbobl_state::bublbobl_mcu_ddr3_w)
{
m_ddr3 = data;
}
READ8_MEMBER(bublbobl_state::bublbobl_mcu_ddr4_r)
{
return m_ddr4;
}
WRITE8_MEMBER(bublbobl_state::bublbobl_mcu_ddr4_w)
{
m_ddr4 = data;
}
READ8_MEMBER(bublbobl_state::bublbobl_mcu_port1_r)
{
//logerror("%04x: 6801U4 port 1 read\n", m_mcu->pc());
m_port1_in = ioport("IN0")->read();
return (m_port1_out & m_ddr1) | (m_port1_in & ~m_ddr1);
}
WRITE8_MEMBER(bublbobl_state::bublbobl_mcu_port1_w)
{
//logerror("%04x: 6801U4 port 1 write %02x\n", m_mcu->pc(), data);
// bit 4: coin lockout
machine().bookkeeping().coin_lockout_global_w(~data & 0x10);
// bit 5: select 1-way or 2-way coin counter
// bit 6: trigger IRQ on main CPU (jumper switchable to vblank)
// trigger on high->low transition
if ((m_port1_out & 0x40) && (~data & 0x40))
{
// logerror("triggering IRQ on main CPU\n");
m_maincpu->set_input_line_vector(0, m_mcu_sharedram[0]);
m_maincpu->set_input_line(0, HOLD_LINE);
}
// bit 7: select read or write shared RAM
m_port1_out = data;
}
READ8_MEMBER(bublbobl_state::bublbobl_mcu_port2_r)
{
//logerror("%04x: 6801U4 port 2 read\n", m_mcu->pc());
return (m_port2_out & m_ddr2) | (m_port2_in & ~m_ddr2);
}
WRITE8_MEMBER(bublbobl_state::bublbobl_mcu_port2_w)
{
//logerror("%04x: 6801U4 port 2 write %02x\n", m_mcu->pc(), data);
static const char *const portnames[] = { "DSW0", "DSW1", "IN1", "IN2" };
// bits 0-3: bits 8-11 of shared RAM address
// bit 4: clock (goes to PAL A78-04.12)
// latch on low->high transition
if ((~m_port2_out & 0x10) && (data & 0x10))
{
int address = m_port4_out | ((data & 0x0f) << 8);
if (m_port1_out & 0x80)
{
// read
if ((address & 0x0800) == 0x0000)
m_port3_in = ioport(portnames[address & 3])->read();
else if ((address & 0x0c00) == 0x0c00)
m_port3_in = m_mcu_sharedram[address & 0x03ff];
// logerror("reading %02x from shared RAM %04x\n", m_port3_in, address);
}
else
{
// write
// logerror("writing %02x to shared RAM %04x\n", m_port3_out, address);
if ((address & 0x0c00) == 0x0c00)
m_mcu_sharedram[address & 0x03ff] = m_port3_out;
}
}
m_port2_out = data;
}
READ8_MEMBER(bublbobl_state::bublbobl_mcu_port3_r)
{
//logerror("%04x: 6801U4 port 3 read\n", m_mcu->pc());
return (m_port3_out & m_ddr3) | (m_port3_in & ~m_ddr3);
}
WRITE8_MEMBER(bublbobl_state::bublbobl_mcu_port3_w)
{
//logerror("%04x: 6801U4 port 3 write %02x\n", m_mcu->pc(), data);
m_port3_out = data;
}
READ8_MEMBER(bublbobl_state::bublbobl_mcu_port4_r)
{
//logerror("%04x: 6801U4 port 4 read\n", m_mcu->pc());
return (m_port4_out & m_ddr4) | (m_port4_in & ~m_ddr4);
}
WRITE8_MEMBER(bublbobl_state::bublbobl_mcu_port4_w)
{
//logerror("%04x: 6801U4 port 4 write %02x\n", m_mcu->pc(), data);
// bits 0-7 of shared RAM address
m_port4_out = data;
}
/***************************************************************************
Bobble Bobble protection (IC43). This appears to be a PAL.
Note: the checks on the values returned by ic43_b_r are actually patched out
in boblbobl, so they don't matter. All checks are patched out in sboblbob.
***************************************************************************/
READ8_MEMBER(bublbobl_state::boblbobl_ic43_a_r)
{
// if (offset >= 2)
// logerror("%04x: ic43_a_r (offs %d) res = %02x\n", m_mcu->pc(), offset, res);
if (offset == 0)
return m_ic43_a << 4;
else
return machine().rand() & 0xff;
}
WRITE8_MEMBER(bublbobl_state::boblbobl_ic43_a_w)
{
int res = 0;
switch (offset)
{
case 0:
if (~m_ic43_a & 8) res ^= 1;
if (~m_ic43_a & 1) res ^= 2;
if (~m_ic43_a & 1) res ^= 4;
if (~m_ic43_a & 2) res ^= 4;
if (~m_ic43_a & 4) res ^= 8;
break;
case 1:
if (~m_ic43_a & 8) res ^= 1;
if (~m_ic43_a & 2) res ^= 1;
if (~m_ic43_a & 8) res ^= 2;
if (~m_ic43_a & 1) res ^= 4;
if (~m_ic43_a & 4) res ^= 8;
break;
case 2:
if (~m_ic43_a & 4) res ^= 1;
if (~m_ic43_a & 8) res ^= 2;
if (~m_ic43_a & 2) res ^= 4;
if (~m_ic43_a & 1) res ^= 8;
if (~m_ic43_a & 4) res ^= 8;
break;
case 3:
if (~m_ic43_a & 2) res ^= 1;
if (~m_ic43_a & 4) res ^= 2;
if (~m_ic43_a & 8) res ^= 2;
if (~m_ic43_a & 8) res ^= 4;
if (~m_ic43_a & 1) res ^= 8;
break;
}
m_ic43_a = res;
}
WRITE8_MEMBER(bublbobl_state::boblbobl_ic43_b_w)
{
static const int xorval[4] = { 4, 1, 8, 2 };
// logerror("%04x: ic43_b_w (offs %d) %02x\n", m_mcu->pc(), offset, data);
m_ic43_b = (data >> 4) ^ xorval[offset];
}
READ8_MEMBER(bublbobl_state::boblbobl_ic43_b_r)
{
// logerror("%04x: ic43_b_r (offs %d)\n", m_mcu->pc(), offset);
if (offset == 0)
return m_ic43_b << 4;
else
return 0xff; // not used?
}
/***************************************************************************
Bootleg Bubble Bobble 68705 protection interface
This is used by the 68705 bootleg version. Note that this actually
wasn't working 100%, for some unknown reason the enemy movement wasn't right.
The following is ENTIRELY GUESSWORK!!!
***************************************************************************/
INTERRUPT_GEN_MEMBER(bub68705_state::bublbobl_m68705_interrupt)
{
device.execute().set_input_line(M68705_IRQ_LINE, ASSERT_LINE);
timer_set(attotime::from_msec(1000/60), TIMER_M68705_IRQ_ACK); /* TODO: understand how this is ack'ed */
}
WRITE8_MEMBER(bub68705_state::port_a_w)
{
//logerror("%04x: 68705 port A write %02x\n", m_mcu->pc(), data);
m_port_a_out = data;
}
/*
* Port B connections:
*
* all bits are logical 1 when read (+5V pullup)
*
* 0 W enables latch which holds data from main Z80 memory
* 1 W loads the latch which holds the low 8 bits of the address of
* the main Z80 memory location to access
* 2 W loads the latch which holds the high 4 bits of the address of
* the main Z80 memory location to access
* 00-07 = read input ports
* 0c-0f = access z80 memory at 0xfc00
* 3 W selects Z80 memory access direction (0 = write 1 = read)
* 4 W clocks main Z80 memory access (goes to a PAL)
* 5 W clocks a flip-flop which causes IRQ on the main Z80
* 6 W not used?
* 7 W not used?
*/
WRITE8_MEMBER(bub68705_state::port_b_w)
{
//logerror("%04x: 68705 port B write %02x\n", m_mcu->pc(), data);
if (BIT(mem_mask, 0) && !BIT(data, 0) && BIT(m_port_b_out, 0))
m_mcu->pa_w(space, 0, m_latch);
if (BIT(mem_mask, 1) && BIT(data, 1) && !BIT(m_port_b_out, 1)) /* positive edge trigger */
{
m_address = (m_address & 0xff00) | m_port_a_out;
//logerror("%04x: 68705 address %02x\n", m_mcu->pc(), m_port_a_out);
}
if (BIT(mem_mask, 2) && BIT(data, 2) && !BIT(m_port_b_out, 2)) /* positive edge trigger */
m_address = (m_address & 0x00ff) | ((m_port_a_out & 0x0f) << 8);
if (BIT(mem_mask, 4) && !BIT(data, 4) && BIT(m_port_b_out, 4))
{
if (BIT(data, 3)) /* read */
{
if ((m_address & 0x0800) == 0x0000)
{
//logerror("%04x: 68705 read input port %02x\n", m_mcu->pc(), m_address);
m_latch = m_mux_ports[m_address & 3]->read();
}
else if ((m_address & 0x0c00) == 0x0c00)
{
//logerror("%04x: 68705 read %02x from address %04x\n", m_mcu->pc(), m_mcu_sharedram[m_address], m_address);
m_latch = m_mcu_sharedram[m_address & 0x03ff];
}
else
{
logerror("%04x: 68705 unknown read address %04x\n", m_mcu->pc(), m_address);
}
}
else /* write */
{
if ((m_address & 0x0c00) == 0x0c00)
{
//logerror("%04x: 68705 write %02x to address %04x\n", m_mcu->pc(), m_port_a_out, m_address);
m_mcu_sharedram[m_address & 0x03ff] = m_port_a_out;
}
else
{
logerror("%04x: 68705 unknown write to address %04x\n", m_mcu->pc(), m_address);
}
}
}
if (BIT(mem_mask, 5) && !BIT(data, 5) && BIT(m_port_b_out, 5))
{
/* hack to get random EXTEND letters (who is supposed to do this? 68705? PAL?) */
m_mcu_sharedram[0x7c] = machine().rand() % 6;
m_maincpu->set_input_line_vector(0, m_mcu_sharedram[0]);
m_maincpu->set_input_line(0, HOLD_LINE);
}
if (BIT(mem_mask, 6) && !BIT(data, 6) && BIT(m_port_b_out, 6))
logerror("%04x: 68705 unknown port B bit %02x\n", m_mcu->pc(), data);
if (BIT(mem_mask, 7) && !BIT(data, 7) && BIT(m_port_b_out, 7))
logerror("%04x: 68705 unknown port B bit %02x\n", m_mcu->pc(), data);
m_port_b_out = data;
}