mame/src/emu/addrmap.cpp
2016-11-27 09:56:49 +11:00

743 lines
25 KiB
C++

// license:BSD-3-Clause
// copyright-holders:Aaron Giles
/***************************************************************************
addrmap.c
Macros and helper functions for handling address map definitions.
***************************************************************************/
#include "emu.h"
#include "validity.h"
//**************************************************************************
// PARAMETERS
//**************************************************************************
#define DETECT_OVERLAPPING_MEMORY (0)
//**************************************************************************
// ADDRESS MAP ENTRY
//**************************************************************************
//-------------------------------------------------
// address_map_entry - constructor
//-------------------------------------------------
address_map_entry::address_map_entry(device_t &device, address_map &map, offs_t start, offs_t end)
: m_next(nullptr),
m_map(map),
m_devbase(device),
m_addrstart(start),
m_addrend(end),
m_addrmirror(0),
m_addrmask(0),
m_addrselect(0),
m_share(nullptr),
m_region(nullptr),
m_rgnoffs(0),
m_submap_bits(0),
m_memory(nullptr),
m_bytestart(0),
m_byteend(0),
m_bytemirror(0),
m_bytemask(0)
{
if (map.m_globalmask != 0 && (start & ~map.m_globalmask) != 0)
{
osd_printf_warning("AS_%d map entry start %08X lies outside global address mask %08X\n", map.m_spacenum, start, map.m_globalmask);
m_addrstart &= map.m_globalmask;
}
if (map.m_globalmask != 0 && (end & ~map.m_globalmask) != 0)
{
osd_printf_warning("AS_%d map entry end %08X lies outside global address mask %08X\n", map.m_spacenum, end, map.m_globalmask);
m_addrend &= map.m_globalmask;
}
}
//-------------------------------------------------
// set_mask - set the mask value
//-------------------------------------------------
address_map_entry &address_map_entry::mask(offs_t _mask)
{
m_addrmask = _mask;
if (m_map.m_globalmask != 0)
m_addrmask &= m_map.m_globalmask;
return *this;
}
//-------------------------------------------------
// set_submap - set up a handler for
// retrieve a submap from a device
//-------------------------------------------------
address_map_entry &address_map_entry::set_submap(const char *tag, address_map_delegate func, int bits, u64 mask)
{
if(!bits)
bits = m_map.m_databits;
assert(unitmask_is_appropriate(bits, mask, func.name()));
m_read.m_type = AMH_DEVICE_SUBMAP;
m_read.m_tag = tag;
m_read.m_mask = mask;
m_write.m_type = AMH_DEVICE_SUBMAP;
m_write.m_tag = tag;
m_write.m_mask = mask;
m_submap_delegate = func;
m_submap_bits = bits;
return *this;
}
//-------------------------------------------------
// set_handler - handler setters for
// 8-bit read/write handlers
//-------------------------------------------------
address_map_entry &address_map_entry::set_handler(read8_delegate func, u64 unitmask)
{
assert(!func.isnull());
assert(unitmask_is_appropriate(8, unitmask, func.name()));
m_read.m_type = AMH_DEVICE_DELEGATE;
m_read.m_bits = 8;
m_read.m_mask = unitmask;
m_read.m_name = func.name();
m_rproto8 = func;
return *this;
}
address_map_entry &address_map_entry::set_handler(write8_delegate func, u64 unitmask)
{
assert(!func.isnull());
assert(unitmask_is_appropriate(8, unitmask, func.name()));
m_write.m_type = AMH_DEVICE_DELEGATE;
m_write.m_bits = 8;
m_write.m_mask = unitmask;
m_write.m_name = func.name();
m_wproto8 = func;
return *this;
}
address_map_entry &address_map_entry::set_handler(read8_delegate rfunc, write8_delegate wfunc, u64 unitmask)
{
set_handler(rfunc, unitmask);
set_handler(wfunc, unitmask);
return *this;
}
//-------------------------------------------------
// set_handler - handler setters for
// 16-bit read/write handlers
//-------------------------------------------------
address_map_entry &address_map_entry::set_handler(read16_delegate func, u64 unitmask)
{
assert(!func.isnull());
assert(unitmask_is_appropriate(16, unitmask, func.name()));
m_read.m_type = AMH_DEVICE_DELEGATE;
m_read.m_bits = 16;
m_read.m_mask = unitmask;
m_read.m_name = func.name();
m_rproto16 = func;
return *this;
}
address_map_entry &address_map_entry::set_handler(write16_delegate func, u64 unitmask)
{
assert(!func.isnull());
assert(unitmask_is_appropriate(16, unitmask, func.name()));
m_write.m_type = AMH_DEVICE_DELEGATE;
m_write.m_bits = 16;
m_write.m_mask = unitmask;
m_write.m_name = func.name();
m_wproto16 = func;
return *this;
}
address_map_entry &address_map_entry::set_handler(read16_delegate rfunc, write16_delegate wfunc, u64 unitmask)
{
set_handler(rfunc, unitmask);
set_handler(wfunc, unitmask);
return *this;
}
//-------------------------------------------------
// set_handler - handler setters for
// 32-bit read/write handlers
//-------------------------------------------------
address_map_entry &address_map_entry::set_handler(read32_delegate func, u64 unitmask)
{
assert(!func.isnull());
assert(unitmask_is_appropriate(32, unitmask, func.name()));
m_read.m_type = AMH_DEVICE_DELEGATE;
m_read.m_bits = 32;
m_read.m_mask = unitmask;
m_read.m_name = func.name();
m_rproto32 = func;
return *this;
}
address_map_entry &address_map_entry::set_handler(write32_delegate func, u64 unitmask)
{
assert(!func.isnull());
assert(unitmask_is_appropriate(32, unitmask, func.name()));
m_write.m_type = AMH_DEVICE_DELEGATE;
m_write.m_bits = 32;
m_write.m_mask = unitmask;
m_write.m_name = func.name();
m_wproto32 = func;
return *this;
}
address_map_entry &address_map_entry::set_handler(read32_delegate rfunc, write32_delegate wfunc, u64 unitmask)
{
set_handler(rfunc, unitmask);
set_handler(wfunc, unitmask);
return *this;
}
//-------------------------------------------------
// set_handler - handler setters for
// 64-bit read/write handlers
//-------------------------------------------------
address_map_entry &address_map_entry::set_handler(read64_delegate func, u64 unitmask)
{
assert(!func.isnull());
assert(unitmask_is_appropriate(64, unitmask, func.name()));
m_read.m_type = AMH_DEVICE_DELEGATE;
m_read.m_bits = 64;
m_read.m_mask = 0;
m_read.m_name = func.name();
m_rproto64 = func;
return *this;
}
address_map_entry &address_map_entry::set_handler(write64_delegate func, u64 unitmask)
{
assert(!func.isnull());
assert(unitmask_is_appropriate(64, unitmask, func.name()));
m_write.m_type = AMH_DEVICE_DELEGATE;
m_write.m_bits = 64;
m_write.m_mask = 0;
m_write.m_name = func.name();
m_wproto64 = func;
return *this;
}
address_map_entry &address_map_entry::set_handler(read64_delegate rfunc, write64_delegate wfunc, u64 unitmask)
{
set_handler(rfunc, unitmask);
set_handler(wfunc, unitmask);
return *this;
}
//-------------------------------------------------
// set_handler - handler setter for setoffset
//-------------------------------------------------
address_map_entry &address_map_entry::set_handler(setoffset_delegate func)
{
assert(!func.isnull());
m_setoffsethd.m_type = AMH_DEVICE_DELEGATE;
m_setoffsethd.m_bits = 0;
m_setoffsethd.m_mask = 0;
m_setoffsethd.m_name = func.name();
m_soproto = func;
return *this;
}
//-------------------------------------------------
// unitmask_is_appropriate - verify that the
// provided unitmask is valid and expected
//-------------------------------------------------
bool address_map_entry::unitmask_is_appropriate(u8 width, u64 unitmask, const char *string)
{
// if no mask, this must match the default width of the map
if (unitmask == 0)
{
if (m_map.m_databits != width)
throw emu_fatalerror("Handler %s is a %d-bit handler but was specified in a %d-bit address map", string, width, m_map.m_databits);
return true;
}
// if we have a mask, we must be smaller than the default width of the map
if (m_map.m_databits < width)
throw emu_fatalerror("Handler %s is a %d-bit handler and is too wide to be used in a %d-bit address map", string, width, m_map.m_databits);
// the mask must represent whole units of width
u32 basemask = (width == 8) ? 0xff : (width == 16) ? 0xffff : 0xffffffff;
u64 singlemask = basemask;
while (singlemask != 0)
{
if ((unitmask & singlemask) != 0 && (unitmask & singlemask) != singlemask)
throw emu_fatalerror("Handler %s specified a mask of %08X%08X; needs to be in even chunks of %X", string, (u32)(unitmask >> 32), (u32)unitmask, basemask);
singlemask <<= width;
}
return true;
}
//**************************************************************************
// ADDRESS MAP
//**************************************************************************
//-------------------------------------------------
// address_map - constructor
//-------------------------------------------------
address_map::address_map(device_t &device, address_spacenum spacenum)
: m_spacenum(spacenum),
m_device(&device),
m_databits(0xff),
m_unmapval(0),
m_globalmask(0)
{
// get our memory interface
const device_memory_interface *memintf;
if (!m_device->interface(memintf))
throw emu_fatalerror("No memory interface defined for device '%s'\n", m_device->tag());
// and then the configuration for the current address space
const address_space_config *spaceconfig = memintf->space_config(spacenum);
if (spaceconfig == nullptr)
throw emu_fatalerror("No memory address space configuration found for device '%s', space %d\n", m_device->tag(), spacenum);
// construct the internal device map (first so it takes priority)
if (spaceconfig->m_internal_map != nullptr)
(*spaceconfig->m_internal_map)(*this);
if (!spaceconfig->m_internal_map_delegate.isnull())
spaceconfig->m_internal_map_delegate(*this);
// append the map provided by the owner
if (memintf->address_map(spacenum) != nullptr)
{
m_device = device.owner();
(*memintf->address_map(spacenum))(*this);
m_device = &device;
}
else
{
// if the owner didn't provide a map, use the default device map
if (spaceconfig->m_default_map != nullptr)
(*spaceconfig->m_default_map)(*this);
if (!spaceconfig->m_default_map_delegate.isnull())
spaceconfig->m_default_map_delegate(*this);
}
}
//-------------------------------------------------
// address_map - constructor in the submap case
//-------------------------------------------------
address_map::address_map(device_t &device, address_map_entry *entry)
: m_spacenum(AS_PROGRAM),
m_device(&device),
m_databits(0xff),
m_unmapval(0),
m_globalmask(0)
{
// Retrieve the submap
entry->m_submap_delegate.late_bind(*m_device);
entry->m_submap_delegate(*this);
}
//----------------------------------------------------------
// address_map - constructor dynamic device mapping case
//----------------------------------------------------------
address_map::address_map(const address_space &space, offs_t start, offs_t end, int bits, u64 unitmask, device_t &device, address_map_delegate submap_delegate)
: m_spacenum(space.spacenum()),
m_device(&device),
m_databits(space.data_width()),
m_unmapval(space.unmap()),
m_globalmask(space.bytemask())
{
range(start, end).set_submap(DEVICE_SELF, submap_delegate, bits, unitmask);
}
//-------------------------------------------------
// ~address_map - destructor
//-------------------------------------------------
address_map::~address_map()
{
}
//-------------------------------------------------
// configure - either configure the space and
// databits, or verify they match previously-set
// values
//-------------------------------------------------
void address_map::configure(address_spacenum spacenum, u8 databits)
{
assert(m_spacenum == spacenum);
if (m_databits == 0xff)
m_databits = databits;
else
assert(m_databits == databits);
}
//-------------------------------------------------
// append - append an entry to the end of the
// list
//-------------------------------------------------
void address_map::global_mask(offs_t mask)
{
// if (m_entrylist != nullptr)
// throw emu_fatalerror("AM_GLOBALMASK must be specified before any entries");
m_globalmask = mask;
}
//-------------------------------------------------
// add - add a new entry
//-------------------------------------------------
address_map_entry &address_map::range(offs_t start, offs_t end)
{
address_map_entry *ptr = global_alloc(address_map_entry(*m_device, *this, start, end));
m_entrylist.append(*ptr);
return *ptr;
}
//-------------------------------------------------
// uplift_submaps - propagate in the device submaps
//-------------------------------------------------
void address_map::uplift_submaps(running_machine &machine, device_t &owner, endianness_t endian)
{
address_map_entry *prev = nullptr;
address_map_entry *entry = m_entrylist.first();
while (entry)
{
if (entry->m_read.m_type == AMH_DEVICE_SUBMAP)
{
std::string tag = owner.subtag(entry->m_read.m_tag);
device_t *mapdevice = machine.device(tag.c_str());
if (mapdevice == nullptr) {
throw emu_fatalerror("Attempted to submap a non-existent device '%s' in space %d of device '%s'\n", tag.c_str(), m_spacenum, m_device->basetag());
}
// Grab the submap
address_map submap(*mapdevice, entry);
// Recursively uplift it if needed
submap.uplift_submaps(machine, *mapdevice, endian);
// Compute the unit repartition characteristics
int entry_bits = entry->m_submap_bits;
if (!entry_bits)
entry_bits = m_databits;
if (submap.m_databits != entry_bits)
throw emu_fatalerror("AM_DEVICE wants a %d bits large address map and got a %d bits large one instead.\n", entry_bits, submap.m_databits);
int entry_bytes = entry_bits / 8;
int databytes = m_databits / 8;
offs_t mirror_address_mask = (databytes - 1) & ~(entry_bytes - 1);
u64 entry_mask = (2ULL << (entry_bits-1)) - 1;
int slot_offset[8];
int slot_count = 0;
int max_slot_count = m_databits / entry_bits;
int slot_xor_mask = endian == ENDIANNESS_LITTLE ? 0 : max_slot_count - 1;
u64 global_mask = entry->m_read.m_mask;
// zero means all
if (!global_mask)
global_mask = ~global_mask;
// mask consistency has already been checked in
// unitmask_is_appropriate, so one bit is enough
for (int slot=0; slot < max_slot_count; slot++)
if (global_mask & (1ULL << ((slot ^ slot_xor_mask) * entry_bits)))
slot_offset[slot_count++] = (slot ^ slot_xor_mask) * entry_bits;
// Merge in all the map contents in order
while (submap.m_entrylist.count())
{
address_map_entry *subentry = submap.m_entrylist.detach_head();
// Remap start and end
unsigned int start_offset = subentry->m_addrstart / entry_bytes;
unsigned int start_slot = start_offset % slot_count;
subentry->m_addrstart = entry->m_addrstart + (start_offset / slot_count) * databytes;
// Drop the entry if it ends up outside the range
if (subentry->m_addrstart > entry->m_addrend)
{
global_free(subentry);
continue;
}
unsigned int end_offset = subentry->m_addrend / entry_bytes;
unsigned int end_slot = end_offset % slot_count;
subentry->m_addrend = entry->m_addrstart + (end_offset / slot_count) * databytes + databytes - 1;
// Clip the entry to the end of the range
if (subentry->m_addrend > entry->m_addrend || subentry->m_addrend < entry->m_addrstart)
subentry->m_addrend = entry->m_addrend;
// Detect special unhandled case (range straddling
// slots, requiring splitting in multiple entries and
// unimplemented offset-add subunit handler)
if (subentry->m_addrstart + databytes - 1 != subentry->m_addrend &&
(start_slot != 0 || end_slot != slot_count - 1))
throw emu_fatalerror("uplift_submaps unhandled case: range straddling slots.\n");
if (entry->m_addrmask || subentry->m_addrmask)
throw emu_fatalerror("uplift_submaps unhandled case: address masks.\n");
if (entry->m_addrselect || subentry->m_addrselect)
throw emu_fatalerror("uplift_submaps unhandled case: select masks.\n");
if (subentry->m_addrmirror & mirror_address_mask)
throw emu_fatalerror("uplift_submaps unhandled case: address mirror bit within subentry.\n");
subentry->m_addrmirror |= entry->m_addrmirror;
// Twiddle the unitmask on the data accessors that need it
for (int data_entry = 0; data_entry < 3; data_entry++)
{
map_handler_data &mdata = (data_entry==0)? subentry->m_read : ((data_entry==1)? subentry->m_write : subentry->m_setoffsethd);
if (mdata.m_type == AMH_NONE)
continue;
if (mdata.m_type != AMH_DEVICE_DELEGATE && mdata.m_type != AMH_NOP)
throw emu_fatalerror("Only normal read/write methods are accepted in device submaps.\n");
if (mdata.m_bits == 0 && entry_bits != m_databits)
mdata.m_bits = entry_bits;
u64 mask = 0;
if (mdata.m_bits != m_databits)
{
u64 unitmask = mdata.m_mask ? mdata.m_mask : entry_mask;
for (int slot = start_slot; slot <= end_slot; slot++)
mask |= unitmask << slot_offset[slot];
}
mdata.m_mask = mask;
}
// Insert the entry in the map
m_entrylist.insert_after(*subentry, prev);
prev = subentry;
}
address_map_entry *to_delete = entry;
entry = entry->next();
m_entrylist.remove(*to_delete);
}
else
{
prev = entry;
entry = entry->next();
}
}
}
//-------------------------------------------------
// map_validity_check - perform validity checks on
// one of the device's address maps
//-------------------------------------------------
void address_map::map_validity_check(validity_checker &valid, address_spacenum spacenum) const
{
// it's safe to assume here that the device has a memory interface and a config for this space
const address_space_config &spaceconfig = *m_device->memory().space_config(spacenum);
int datawidth = spaceconfig.m_databus_width;
int alignunit = datawidth / 8;
bool detected_overlap = DETECT_OVERLAPPING_MEMORY ? false : true;
// if this is an empty map, just ignore it
if (m_entrylist.first() == nullptr)
return;
// validate the global map parameters
if (m_spacenum != spacenum)
osd_printf_error("Space %d has address space %d handlers!\n", spacenum, m_spacenum);
if (m_databits != datawidth)
osd_printf_error("Wrong memory handlers provided for %s space! (width = %d, memory = %08x)\n", spaceconfig.m_name, datawidth, m_databits);
// loop over entries and look for errors
for (address_map_entry &entry : m_entrylist)
{
u32 bytestart = spaceconfig.addr2byte(entry.m_addrstart);
u32 byteend = spaceconfig.addr2byte_end(entry.m_addrend);
// look for overlapping entries
if (!detected_overlap)
{
for (address_map_entry &scan : m_entrylist)
{
if (&scan == &entry)
break;
if (entry.m_addrstart <= scan.m_addrend && entry.m_addrend >= scan.m_addrstart &&
((entry.m_read.m_type != AMH_NONE && scan.m_read.m_type != AMH_NONE) ||
(entry.m_write.m_type != AMH_NONE && scan.m_write.m_type != AMH_NONE)))
{
osd_printf_warning("%s space has overlapping memory (%X-%X,%d,%d) vs (%X-%X,%d,%d)\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_read.m_type, entry.m_write.m_type, scan.m_addrstart, scan.m_addrend, scan.m_read.m_type, scan.m_write.m_type);
detected_overlap = true;
break;
}
}
}
// look for inverted start/end pairs
if (byteend < bytestart)
osd_printf_error("Wrong %s memory read handler start = %08x > end = %08x\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend);
// look for misaligned entries
if ((bytestart & (alignunit - 1)) != 0 || (byteend & (alignunit - 1)) != (alignunit - 1))
osd_printf_error("Wrong %s memory read handler start = %08x, end = %08x ALIGN = %d\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, alignunit);
// verify mask/mirror/select
offs_t set_bits = entry.m_addrstart | entry.m_addrend;
offs_t changing_bits = entry.m_addrstart ^ entry.m_addrend;
changing_bits |= changing_bits >> 1;
changing_bits |= changing_bits >> 2;
changing_bits |= changing_bits >> 4;
changing_bits |= changing_bits >> 8;
changing_bits |= changing_bits >> 16;
if (entry.m_addrmask & ~changing_bits)
osd_printf_error("In %s memory range %x-%x, mask %x is trying to unmask an unchanging address bit (%x)\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_addrmask, entry.m_addrmask & ~changing_bits);
if (entry.m_addrmirror & changing_bits)
osd_printf_error("In %s memory range %x-%x, mirror %x touches a changing address bit (%x)\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_addrmirror, entry.m_addrmirror & changing_bits);
if (entry.m_addrselect & changing_bits)
osd_printf_error("In %s memory range %x-%x, select %x touches a changing address bit (%x)\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_addrselect, entry.m_addrselect & changing_bits);
if (entry.m_addrmirror & set_bits)
osd_printf_error("In %s memory range %x-%x, mirror %x touches a set address bit (%x)\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_addrmirror, entry.m_addrmirror & set_bits);
if (entry.m_addrselect & set_bits)
osd_printf_error("In %s memory range %x-%x, select %x touches a set address bit (%x)\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_addrselect, entry.m_addrselect & set_bits);
if (entry.m_addrmirror & entry.m_addrselect)
osd_printf_error("In %s memory range %x-%x, mirror %x touches a select bit (%x)\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_addrmirror, entry.m_addrmirror & entry.m_addrselect);
// if this is a program space, auto-assign implicit ROM entries
if (entry.m_read.m_type == AMH_ROM && entry.m_region == nullptr)
{
entry.m_region = m_device->tag();
entry.m_rgnoffs = entry.m_addrstart;
}
// if this entry references a memory region, validate it
if (entry.m_region != nullptr && entry.m_share == nullptr)
{
// make sure we can resolve the full path to the region
bool found = false;
std::string entry_region = entry.m_devbase.subtag(entry.m_region);
// look for the region
for (device_t &dev : device_iterator(m_device->mconfig().root_device()))
for (const rom_entry *romp = rom_first_region(dev); romp != nullptr && !found; romp = rom_next_region(romp))
{
if (rom_region_name(dev, romp) == entry_region)
{
// verify the address range is within the region's bounds
offs_t length = ROMREGION_GETLENGTH(romp);
if (entry.m_rgnoffs + (byteend - bytestart + 1) > length)
osd_printf_error("%s space memory map entry %X-%X extends beyond region '%s' size (%X)\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_region, length);
found = true;
}
}
// error if not found
if (!found)
osd_printf_error("%s space memory map entry %X-%X references non-existant region '%s'\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, entry.m_region);
}
// make sure all devices exist
if (entry.m_read.m_type == AMH_DEVICE_DELEGATE)
{
// extract the device tag from the proto-delegate
const char *devtag = nullptr;
switch (entry.m_read.m_bits)
{
case 8: devtag = entry.m_rproto8.device_name(); break;
case 16: devtag = entry.m_rproto16.device_name(); break;
case 32: devtag = entry.m_rproto32.device_name(); break;
case 64: devtag = entry.m_rproto64.device_name(); break;
}
if (entry.m_devbase.subdevice(devtag) == nullptr)
osd_printf_error("%s space memory map entry reads from nonexistent device '%s'\n", spaceconfig.m_name,
devtag != nullptr ? devtag : "<unspecified>");
}
if (entry.m_write.m_type == AMH_DEVICE_DELEGATE)
{
// extract the device tag from the proto-delegate
const char *devtag = nullptr;
switch (entry.m_write.m_bits)
{
case 8: devtag = entry.m_wproto8.device_name(); break;
case 16: devtag = entry.m_wproto16.device_name(); break;
case 32: devtag = entry.m_wproto32.device_name(); break;
case 64: devtag = entry.m_wproto64.device_name(); break;
}
if (entry.m_devbase.subdevice(devtag) == nullptr)
osd_printf_error("%s space memory map entry writes to nonexistent device '%s'\n", spaceconfig.m_name,
devtag != nullptr ? devtag : "<unspecified>");
}
if (entry.m_setoffsethd.m_type == AMH_DEVICE_DELEGATE)
{
// extract the device tag from the proto-delegate
const char *devtag = entry.m_soproto.device_name();
if (entry.m_devbase.subdevice(devtag) == nullptr)
osd_printf_error("%s space memory map entry references nonexistent device '%s'\n", spaceconfig.m_name,
devtag != nullptr ? devtag : "<unspecified>");
}
// make sure ports exist
// if ((entry.m_read.m_type == AMH_PORT && entry.m_read.m_tag != nullptr && portlist.find(entry.m_read.m_tag) == nullptr) ||
// (entry.m_write.m_type == AMH_PORT && entry.m_write.m_tag != nullptr && portlist.find(entry.m_write.m_tag) == nullptr))
// osd_printf_error("%s space memory map entry references nonexistent port tag '%s'\n", spaceconfig.m_name, entry.m_read.m_tag);
// validate bank and share tags
if (entry.m_read.m_type == AMH_BANK)
valid.validate_tag(entry.m_read.m_tag);
if (entry.m_write.m_type == AMH_BANK)
valid.validate_tag(entry.m_write.m_tag);
if (entry.m_share != nullptr)
valid.validate_tag(entry.m_share);
}
}