mame/src/emu/addrmap.cpp
Olivier Galibert c46e1007a8 emumem: API change [O. Galibert]
* direct_read_data is now a template which takes the address bus shift
  as a parameter.

* address_space::direct<shift>() is now a template method that takes
  the shift as a parameter and returns a pointer instead of a
  reference

* the address to give to {read|write}_* on address_space or
  direct_read_data is now the address one wants to access

Longer explanation:

Up until now, the {read|write}_* methods required the caller to give
the byte offset instead of the actual address.  That's the same on
byte-addressing CPUs, e.g. the ones everyone knows, but it's different
on the word/long/quad addressing ones (tms, sharc, etc...) or the
bit-addressing one (tms340x0).  Changing that required templatizing
the direct access interface on the bus addressing granularity,
historically called address bus shift.  Also, since everybody was
taking the address of the reference returned by direct(), and
structurally didn't have much choice in the matter, it got changed to
return a pointer directly.

Longest historical explanation:

In a cpu core, the hottest memory access, by far, is the opcode
fetching.  It's also an access with very good locality (doesn't move
much, tends to stay in the same rom/ram zone even when jumping around,
tends not to hit handlers), which makes efficient caching worthwhile
(as in, 30-50% faster core iirc on something like the 6502, but that
was 20 years ago and a number of things changed since then).  In fact,
opcode fetching was, in the distant past, just an array lookup indexed
by pc on an offset pointer, which was updated on branches.  It didn't
stay that way because more elaborate access is often needed (handlers,
banking with instructions crossing a bank...) but it still ends up with
a frontend of "if the address is still in the current range read from
pointer+address otherwise do the slowpath", e.g. two usually correctly
predicted branches plus the read most of the time.

Then the >8 bits cpus arrived.  That was ok, it just required to do
the add to a u8 *, then convert to a u16/u32 * and do the read.  At
the asm level, it was all identical except for the final read, and
read_byte/word/long being separate there was no test (and associated
overhead) added in the path.

Then the word-addressing CPUs arrived with, iirc, the tms cpus used in
atari games.  They require, to read from the pointer, to shift the
address, either explicitely, or implicitely through indexing a u16 *.
There were three possibilities:

1- create a new read_* method for each size and granularity.  That
   amounts to a lot of copy/paste in the end, and functions with
   identical prototypes so the compiler can't detect you're using the
   wrong one.

2- put a variable shift in the read path.  That was too expensive
   especially since the most critical cpus are byte-addressing (68000 at
   the time was the key).  Having bit-adressing cpus which means the
   shift can either be right or left depending on the variable makes
   things even worse.

3- require the caller to do the shift himself when needed.

The last solution was chosen, and starting that day the address was a
byte offset and not the real address.  Which is, actually, quite
surprising when writing a new cpu core or, worse, when using the
read/write methods from the driver code.

But since then, C++ happened.  And, in particular, templates with
non-type parameters.  Suddendly, solution 1 can be done without the
copy/paste and with different types allowing to detect (at runtime,
but systematically and at startup) if you got it wrong, while still
generating optimal code.  So it was time to switch to that solution
and makes the address parameter sane again.  Especially since it makes
mucking in the rest of the memory subsystem code a lot more
understandable.
2017-11-29 10:32:31 +01:00

784 lines
27 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)
{
}
//-------------------------------------------------
// set_mask - set the mask value
//-------------------------------------------------
address_map_entry &address_map_entry::mask(offs_t _mask)
{
printf("mask %x\n", _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) const
{
// if no mask, this must match the default width of the map
if (unitmask == 0)
{
if (m_map.m_databits != width)
{
osd_printf_error("Handler %s is a %d-bit handler but was specified in a %d-bit address map\n", string, width, m_map.m_databits);
return false;
}
return true;
}
// if we have a mask, we must be smaller than the default width of the map
if (m_map.m_databits < width)
{
osd_printf_error("Handler %s is a %d-bit handler and is too wide to be used in a %d-bit address map\n", string, width, m_map.m_databits);
return false;
}
// if map is narrower than 64 bits, check the mask width as well
if (m_map.m_databits < 64 && (unitmask >> m_map.m_databits) != 0)
{
osd_printf_error("Handler %s specified a mask of %08X%08X, too wide to be used in a %d-bit address map\n", string, (u32)(unitmask >> 32), (u32)unitmask, m_map.m_databits);
return false;
}
// the mask must represent whole units of width
u32 basemask = (width == 8) ? 0xff : (width == 16) ? 0xffff : 0xffffffff;
u64 singlemask = basemask;
int count = 0;
while (singlemask != 0)
{
if ((unitmask & singlemask) == singlemask)
count++;
else if ((unitmask & singlemask) != 0)
{
osd_printf_error("Handler %s specified a mask of %08X%08X; needs to be in even chunks of %X\n", string, (u32)(unitmask >> 32), (u32)unitmask, basemask);
return false;
}
singlemask <<= width;
}
#if 0
// the mask must be symmetrical
u64 unitmask_bh = unitmask >> 8 & 0x00ff00ff00ff00ffU;
u64 unitmask_bl = unitmask & 0x00ff00ff00ff00ffU;
u64 unitmask_wh = unitmask >> 16 & 0x0000ffff0000ffffU;
u64 unitmask_wl = unitmask & 0x0000ffff0000ffffU;
u64 unitmask_dh = unitmask >> 32 & 0x00000000ffffffffU;
u64 unitmask_dl = unitmask & 0x00000000ffffffffU;
if ((unitmask_bh != 0 && unitmask_bl != 0 && unitmask_bh != unitmask_bl)
|| (unitmask_wh != 0 && unitmask_wl != 0 && unitmask_wh != unitmask_wl)
|| (unitmask_dh != 0 && unitmask_dl != 0 && unitmask_dh != unitmask_dl))
{
osd_printf_error("Handler %s specified an asymmetrical mask of %08X%08X\n", string, (u32)(unitmask >> 32), (u32)unitmask);
return false;
}
#endif
return true;
}
//**************************************************************************
// ADDRESS MAP
//**************************************************************************
//-------------------------------------------------
// address_map - constructor
//-------------------------------------------------
address_map::address_map(device_t &device, int 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.addrmask())
{
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(int, u8 databits)
{
if (m_databits == 0xff)
m_databits = databits;
else if (databits != m_databits)
osd_printf_error("Space %d configured with %d data bits when %d expected\n", m_spacenum, databits, m_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, int 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 = spaceconfig.alignment();
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);
offs_t globalmask = 0xffffffffUL >> (32 - spaceconfig.m_addrbus_width);
if (m_globalmask != 0)
globalmask = m_globalmask;
// loop over entries and look for errors
for (address_map_entry &entry : m_entrylist)
{
// 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 (entry.m_addrend < entry.m_addrstart)
osd_printf_error("Wrong %s memory read handler start = %08x > end = %08x\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend);
// look for ranges outside the global mask
if (entry.m_addrstart & ~globalmask)
osd_printf_error("In %s memory range %x-%x, start address is outside of the global address mask %x\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, globalmask);
if (entry.m_addrend & ~globalmask)
osd_printf_error("In %s memory range %x-%x, end address is outside of the global address mask %x\n", spaceconfig.m_name, entry.m_addrstart, entry.m_addrend, globalmask);
// look for misaligned entries
if ((entry.m_addrstart & (alignunit - 1)) != 0 || (entry.m_addrend & (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)
{
// address map entries that reference regions but are NOPs are pointless
if (entry.m_read.m_type == AMH_NONE && entry.m_write.m_type == AMH_NONE)
osd_printf_error("%s space references memory region %s, but is AM_NOP\n", spaceconfig.m_name, entry.m_region);
// 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 (romload::region const &region : romload::entries(dev.rom_region()).get_regions())
{
if (dev.subtag(region.get_tag()) == entry_region)
{
// verify the address range is within the region's bounds
offs_t const length = region.get_length();
if (entry.m_rgnoffs + spaceconfig.addr2byte(entry.m_addrend - entry.m_addrstart + 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>");
#ifndef MAME_DEBUG // assert will catch this earlier
(void)entry.unitmask_is_appropriate(entry.m_read.m_bits, entry.m_read.m_mask, entry.m_read.m_name);
#endif
}
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>");
#ifndef MAME_DEBUG // assert will catch this earlier
(void)entry.unitmask_is_appropriate(entry.m_write.m_bits, entry.m_write.m_mask, entry.m_write.m_name);
#endif
}
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);
}
}