// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
Sega 315-5195 memory mapper
***************************************************************************/
#include "emu.h"
#include "315_5195.h"
#include <algorithm>
//**************************************************************************
// DEBUGGING
//**************************************************************************
#define VERBOSE (0)
#include "logmacro.h"
//**************************************************************************
// CONSTANTS
//**************************************************************************
// device type definition
DEFINE_DEVICE_TYPE(SEGA_315_5195_MEM_MAPPER, sega_315_5195_mapper_device, "sega_315_5195", "Sega 315-5195 Memory Mapper")
//**************************************************************************
// 315-5195 MEMORY MAPPER
//**************************************************************************
//-------------------------------------------------
// sega_315_5195_mapper_device - constructor
//-------------------------------------------------
sega_315_5195_mapper_device::sega_315_5195_mapper_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, SEGA_315_5195_MEM_MAPPER, tag, owner, clock)
, m_cpu(*this, finder_base::DUMMY_TAG)
, m_cpuregion(*this, finder_base::DUMMY_TAG)
, m_mapper(*this)
, m_pbf_callback(*this)
, m_mcu_int_callback(*this)
, m_space(nullptr)
, m_decrypted_space(nullptr)
, m_curregion(0)
, m_to_sound(0)
, m_from_sound(0)
, m_open_bus_recurse(false)
{
}
//-------------------------------------------------
// open_bus_r - return value from reading an
// unmapped address
//-------------------------------------------------
u16 sega_315_5195_mapper_device::open_bus_r()
{
// Unmapped memory returns the last word on the data bus, which is almost always the opcode
// of the next instruction due to prefetch; however, since we may be encrypted, we actually
// need to return the encrypted opcode, not the last decrypted data.
// Believe it or not, this is actually important for Cotton, which has the following evil
// code: btst #0,$7038f7, which tests the low bit of an unmapped address, which thus should
// return the prefetched value.
// prevent recursion
if (!machine().side_effects_disabled())
{
if (m_open_bus_recurse)
return 0xffff;
// read original encrypted memory at that address
m_open_bus_recurse = true;
const u16 result = m_space->read_word(m_cpu->pc());
m_open_bus_recurse = false;
return result;
}
return 0xffff;
}
//-------------------------------------------------
// write - handle a write to the memory mapper
//-------------------------------------------------
void sega_315_5195_mapper_device::write(offs_t offset, u8 data)
{
// wraps every 32 bytes
offset &= 0x1f;
LOG("(Write %02X = %02X)\n", offset, data);
// remember the previous value and swap in the new one
const u8 oldval = m_regs[offset];
m_regs[offset] = data;
// switch off the offset
switch (offset)
{
case 0x02:
// misc commands
// 00 - resume execution after 03
// 03 - maybe controls halt and reset lines together?
if ((oldval ^ m_regs[offset]) & 3)
{
// fd1094_machine_init calls device_reset on the CPU, so we must do this afterwards
m_cpu->set_input_line(INPUT_LINE_RESET, (m_regs[offset] & 3) == 3 ? ASSERT_LINE : CLEAR_LINE);
}
break;
case 0x03:
// write through to the sound chip
machine().scheduler().synchronize(timer_expired_delegate(FUNC(sega_315_5195_mapper_device::write_to_sound), this), data);
break;
case 0x04:
// controls IRQ lines to 68000, negative logic -- write $B to signal IRQ4
if ((m_regs[offset] & 7) != 7)
for (int irqnum = 0; irqnum < 8; irqnum++)
m_cpu->set_input_line(irqnum, (irqnum == (~m_regs[offset] & 7)) ? HOLD_LINE : CLEAR_LINE);
break;
case 0x05:
// read/write control
// 01 - write data latched in 00,01 to 2 * (address in 0A,0B,0C)
// 02 - read data into latches 00,01 from 2 * (address in 07,08,09)
if (data == 0x01)
{
const offs_t addr = (m_regs[0x0a] << 17) | (m_regs[0x0b] << 9) | (m_regs[0x0c] << 1);
m_space->write_word(addr, (m_regs[0x00] << 8) | m_regs[0x01]);
}
else if (data == 0x02)
{
const offs_t addr = (m_regs[0x07] << 17) | (m_regs[0x08] << 9) | (m_regs[0x09] << 1);
const u16 result = m_space->read_word(addr);
m_regs[0x00] = result >> 8;
m_regs[0x01] = result;
}
break;
case 0x07: case 0x08: case 0x09:
// writes here latch a 68000 address for writing
break;
case 0x0a: case 0x0b: case 0x0c:
// writes here latch a 68000 address for reading
break;
case 0x10: case 0x11:
case 0x12: case 0x13:
case 0x14: case 0x15:
case 0x16: case 0x17:
case 0x18: case 0x19:
case 0x1a: case 0x1b:
case 0x1c: case 0x1d:
case 0x1e: case 0x1f:
if (oldval != data)
update_mapping();
break;
default:
logerror("Unknown memory_mapper_w to address %02X = %02X\n", offset, data);
break;
}
}
//-------------------------------------------------
// read - handle a read from the memory mapper
//-------------------------------------------------
u8 sega_315_5195_mapper_device::read(address_space &space, offs_t offset)
{
// wraps every 32 bytes
offset &= 0x1f;
// switch off the offset
switch (offset)
{
case 0x00:
case 0x01:
// data latches - return the values latched
return m_regs[offset];
case 0x02:
// various input bits from the 68000
// 01 - ????
// 02 - ????
// 04 - ????
// 08 - ????
// 40 - set if busy processing a read/write request
// Together, 01+02 == 00 if the 68000 is halted
// Together, 01+02+04+08 == 0F if the 68000 is executing
return (m_regs[0x02] & 3) == 3 ? 0x00 : 0x0f;
case 0x03:
// this returns data that the sound CPU writes
if (!m_mcu_int_callback.isnull() && !machine().side_effects_disabled())
m_mcu_int_callback(CLEAR_LINE);
return m_from_sound;
default:
logerror("Unknown memory_mapper_r from address %02X\n", offset);
break;
}
return (space.data_width() == 8) ? 0xff : open_bus_r();
}
//-------------------------------------------------
// map_as_rom - map a region as ROM data
//-------------------------------------------------
void sega_315_5195_mapper_device::map_as_rom(u32 offset, u32 length, offs_t mirror, const char *bank_name, const char *decrypted_bank_name, offs_t rgnoffset, write16_delegate whandler)
{
// determine parameters
region_info info;
compute_region(info, m_curregion, length, mirror, offset);
LOG("Map %06X-%06X (%06X) as ROM+%06X(%s) with handler=%s\n", info.start, info.end, info.mirror, rgnoffset, bank_name,
whandler.isnull() ? "none" : whandler.name());
// don't map if the start is past the end of the ROM region
const offs_t romsize = m_cpuregion->bytes();
if (rgnoffset < romsize)
{
// clamp the end to the ROM size
offs_t romend = info.end;
if (rgnoffset + romend + 1 - info.start >= romsize)
romend = romsize - 1 - rgnoffset + info.start;
// map now
m_space->install_read_bank(info.start, romend, info.mirror, bank_name);
if (m_decrypted_space)
m_decrypted_space->install_read_bank(info.start, romend, info.mirror, decrypted_bank_name);
// configure the bank
memory_bank *bank = owner()->membank(bank_name);
memory_bank *decrypted_bank = owner()->membank(decrypted_bank_name);
u8 *memptr = m_cpuregion->base() + rgnoffset;
bank->set_base(memptr);
// remember this bank, and decrypt if necessary
m_banks[m_curregion].set(bank, decrypted_bank, info.start, romend, rgnoffset, memptr);
}
// either install a write handler if provided or unmap the region
//
// shdancer relies on this behaviour to prevent a write to ROM from
// falling through to the memory-mapping registers and crashing the
// game during stage 2-4 (see PC:$18a98). Protection maybe?
if (!whandler.isnull())
m_space->install_write_handler(info.start, info.end, 0, info.mirror, 0, whandler);
else
m_space->unmap_write(info.start, info.end | info.mirror);
}
//-------------------------------------------------
// map_as_ram - map a region as RAM, with an
// optional write handler
//-------------------------------------------------
void sega_315_5195_mapper_device::map_as_ram(u32 offset, u32 length, offs_t mirror, const char *bank_share_name, write16_delegate whandler)
{
// determine parameters
region_info info;
compute_region(info, m_curregion, length, mirror, offset);
LOG("Map %06X-%06X (%06X) as RAM(%s) with handler=%s\n", info.start, info.end, info.mirror, bank_share_name,
whandler.isnull() ? "none" : whandler.name());
// map now
m_space->install_read_bank(info.start, info.end, info.mirror, bank_share_name);
// either install a write handler or a write bank, as appropriate
if (!whandler.isnull())
m_space->install_write_handler(info.start, info.end, 0, info.mirror, 0, whandler);
else
m_space->install_write_bank(info.start, info.end, info.mirror, bank_share_name);
// configure the bank
memory_bank *bank = owner()->membank(bank_share_name);
bank->set_base(owner()->memshare(bank_share_name)->ptr());
// clear this rom bank reference
m_banks[m_curregion].clear();
}
//-------------------------------------------------
// map_as_handler - map a region as a pair of
// read write handlers
//-------------------------------------------------
void sega_315_5195_mapper_device::map_as_handler(u32 offset, u32 length, offs_t mirror, read16_delegate rhandler, write16_delegate whandler)
{
// determine parameters
region_info info;
compute_region(info, m_curregion, length, mirror, offset);
LOG("Map %06X-%06X (%06X) as handler read=%s write=%s\n", info.start, info.end, info.mirror,
rhandler.isnull() ? "none" : rhandler.name(),
whandler.isnull() ? "none" : whandler.name());
// install read/write handlers
if (!rhandler.isnull())
m_space->install_read_handler(info.start, info.end, 0, info.mirror, 0, rhandler);
if (!whandler.isnull())
m_space->install_write_handler(info.start, info.end, 0, info.mirror, 0, whandler);
// clear this rom bank reference
m_banks[m_curregion].clear();
}
//-------------------------------------------------
// configure_explicit - explicitly configure the
// memory map
//-------------------------------------------------
void sega_315_5195_mapper_device::configure_explicit(const u8 *map_data)
{
memcpy(&m_regs[0x10], map_data, 0x10);
update_mapping();
}
//-------------------------------------------------
// fd1094_state_change - handle notifications
// of state changes
//-------------------------------------------------
void sega_315_5195_mapper_device::fd1094_state_change(u8 state)
{
// iterate over regions and set the decrypted address of any ROM banks
for (auto & elem : m_banks)
elem.update();
}
//-------------------------------------------------
// write_to_sound - write data for the sound CPU
//-------------------------------------------------
TIMER_CALLBACK_MEMBER(sega_315_5195_mapper_device::write_to_sound)
{
m_to_sound = param;
if (!m_pbf_callback.isnull())
m_pbf_callback(ASSERT_LINE);
}
//-------------------------------------------------
// write_from_sound - handle writes from the
// sound CPU
//-------------------------------------------------
TIMER_CALLBACK_MEMBER(sega_315_5195_mapper_device::write_from_sound)
{
m_from_sound = param;
if (!m_mcu_int_callback.isnull())
m_mcu_int_callback(ASSERT_LINE);
}
//-------------------------------------------------
// pread - sound CPU read handler
//-------------------------------------------------
u8 sega_315_5195_mapper_device::pread()
{
if (!m_pbf_callback.isnull() && !machine().side_effects_disabled())
m_pbf_callback(CLEAR_LINE);
return m_to_sound;
}
//-------------------------------------------------
// pwrite - sound CPU write handler
//-------------------------------------------------
void sega_315_5195_mapper_device::pwrite(u8 data)
{
machine().scheduler().synchronize(timer_expired_delegate(FUNC(sega_315_5195_mapper_device::write_from_sound), this), data);
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void sega_315_5195_mapper_device::device_start()
{
// bind our handlers
m_mapper.resolve();
m_pbf_callback.resolve();
m_mcu_int_callback.resolve();
// if we are mapping an FD1089, tell all the banks
fd1089_base_device *fd1089 = dynamic_cast<fd1089_base_device *>(m_cpu.target());
if (fd1089 != nullptr)
for (auto & elem : m_banks)
elem.set_decrypt(fd1089);
// if we are mapping an FD1094, register for state change notifications and tell all the banks
fd1094_device *fd1094 = dynamic_cast<fd1094_device *>(m_cpu.target());
if (fd1094 != nullptr)
{
fd1094->notify_state_change(fd1094_device::state_change_delegate(&sega_315_5195_mapper_device::fd1094_state_change, this));
for (auto & elem : m_banks)
elem.set_decrypt(fd1094);
}
// find the address space that is to be mapped
m_space = &m_cpu->space(AS_PROGRAM);
if (m_space == nullptr)
throw emu_fatalerror("Unable to find program address space on device '%s'", m_cpu.finder_tag());
m_decrypted_space = m_cpu->has_space(AS_OPCODES) ? &m_cpu->space(AS_OPCODES) : nullptr;
// register for saves
save_item(NAME(m_regs));
save_item(NAME(m_to_sound));
save_item(NAME(m_from_sound));
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void sega_315_5195_mapper_device::device_reset()
{
// hold the CPU in reset
m_cpu->set_input_line(INPUT_LINE_RESET, ASSERT_LINE);
// clear registers and recompute the memory mapping
std::fill(std::begin(m_regs), std::end(m_regs), 0);
update_mapping();
// release the CPU
m_cpu->set_input_line(INPUT_LINE_RESET, CLEAR_LINE);
m_to_sound = 0;
m_from_sound = 0;
if (!m_pbf_callback.isnull())
m_pbf_callback(CLEAR_LINE);
if (!m_mcu_int_callback.isnull())
m_mcu_int_callback(CLEAR_LINE);
}
//-------------------------------------------------
// compute_region - determine region parameters
// based on current configuration registers and
// actual underlying bus connections
//-------------------------------------------------
void sega_315_5195_mapper_device::compute_region(region_info &info, u8 index, u32 length, u32 mirror, u32 offset)
{
static const offs_t region_size_map[4] = { 0x00ffff, 0x01ffff, 0x07ffff, 0x1fffff };
info.size_mask = region_size_map[m_regs[0x10 + 2 * index] & 3];
info.base = (m_regs[0x11 + 2 * index] << 16) & ~info.size_mask;
info.mirror = mirror & info.size_mask;
info.start = info.base + (offset & info.size_mask);
info.end = info.start + std::min(length - 1, info.size_mask);
}
//-------------------------------------------------
// update_mapping - remap the entire CPU address
// space based on updated mappings
//-------------------------------------------------
void sega_315_5195_mapper_device::update_mapping()
{
LOG("---- Remapping:\n");
// first reset everything back to the beginning
m_space->unmap_readwrite(0x000000, 0xffffff);
m_space->install_read_handler(0x000000, 0xffffff, read8m_delegate(*this, FUNC(sega_315_5195_mapper_device::read)), 0x00ff);
m_space->install_write_handler(0x000000, 0xffffff, write8sm_delegate(*this, FUNC(sega_315_5195_mapper_device::write)), 0x00ff);
// loop over the regions
for (int index = 7; index >= 0; index--)
{
// note the current region and call the mapper to find out what to do
m_curregion = index;
m_mapper(*this, index);
}
}
//**************************************************************************
// DECRYPT BANK HELPER CLASS
//**************************************************************************
//-------------------------------------------------
// decrypt_bank - constructor
//-------------------------------------------------
sega_315_5195_mapper_device::decrypt_bank::decrypt_bank()
: m_bank(nullptr)
, m_decrypted_bank(nullptr)
, m_start(0)
, m_end(0)
, m_rgnoffs(~0)
, m_srcptr(nullptr)
, m_fd1089(nullptr)
{
// invalidate all states
reset();
}
//-------------------------------------------------
// ~decrypt_bank - destructor
//-------------------------------------------------
sega_315_5195_mapper_device::decrypt_bank::~decrypt_bank()
{
}
//-------------------------------------------------
// set_decrypt - configure the decryption target
// CPU
//-------------------------------------------------
void sega_315_5195_mapper_device::decrypt_bank::set_decrypt(fd1089_base_device *fd1089)
{
// set the fd1089 pointer
m_fd1089 = fd1089;
// clear out all fd1094 stuff
m_fd1094_cache.reset();
}
void sega_315_5195_mapper_device::decrypt_bank::set_decrypt(fd1094_device *fd1094)
{
// set the fd1094 pointer and allocate a decryption cache
m_fd1094_cache = std::make_unique<fd1094_decryption_cache>(*fd1094);
// clear out all fd1089 stuff
m_fd1089 = nullptr;
m_fd1089_decrypted.clear();
}
//-------------------------------------------------
// set - set the parameters of this bank after
// a change
//-------------------------------------------------
void sega_315_5195_mapper_device::decrypt_bank::set(memory_bank *bank, memory_bank *decrypted_bank, offs_t start, offs_t end, offs_t rgnoffs, u8 *src)
{
// ignore if not encrypted
if (m_fd1089 == nullptr && m_fd1094_cache == nullptr)
return;
// ignore if nothing is changing
if (bank == m_bank && start == m_start && end == m_end && rgnoffs == m_rgnoffs && src == m_srcptr)
return;
// if the start, end, or src change, throw away any cached data
reset();
// update to the current state
m_bank = bank;
m_decrypted_bank = decrypted_bank;
m_start = start;
m_end = end;
m_rgnoffs = rgnoffs;
m_srcptr = src;
// configure the fd1094 cache
if (m_fd1094_cache != nullptr)
m_fd1094_cache->configure(m_start, m_end + 1 - m_start, m_rgnoffs);
// force an update of what we have
update();
}
//-------------------------------------------------
// update - update the decrypted memory base
// if this rom bank has been assigned
//-------------------------------------------------
void sega_315_5195_mapper_device::decrypt_bank::update()
{
// if this isn't a valid state, don't try to do anything
if (m_bank == nullptr || m_srcptr == nullptr)
return;
// fd1089 case
if (m_fd1089 != nullptr)
{
m_fd1089_decrypted.resize((m_end + 1 - m_start) / 2);
m_fd1089->decrypt(m_start, m_end + 1 - m_start, m_rgnoffs, &m_fd1089_decrypted[0], reinterpret_cast<u16 *>(m_srcptr));
m_decrypted_bank->set_base(&m_fd1089_decrypted[0]);
}
// fd1094 case
if (m_fd1094_cache != nullptr)
m_decrypted_bank->set_base(m_fd1094_cache->decrypted_opcodes(m_fd1094_cache->fd1094().state()));
}