fundamental change to show device delegates are configured.
Device delegates are now aware of the current device during
configuration and will resolve string tags relative to it. This means
that device delegates need a device to be supplied on construction so
they can find the machine configuration object. There's a
one-dimensional array helper to make it easier to construct arrays of
device delegates with the same owner. (I didn't make an n-dimensional
one because I didn't hit a use case, but it would be a simple addition.)
There's no more bind_relative_to member - just call resolve() like you
would for a devcb. There's also no need to cast nullptr when creating a
late bind device delegate. The flip side is that for an overloaded or
non-capturing lambda you'll need to cast to the desired type.
There is one less conditional branch in the hot path for calls for
delegates bound to a function pointer of member function pointer. This
comes at the cost of one additional unconditional branch in the hot
path for calls to delegates bound to functoids (lambdas, functions that
don't take an object reference, other callable objects). This applies
to all delegates, not just device delegates.
Address spaces will now print an error message if a late bind error is
encountered while installing a handler. This will give the range and
address range, hopefully making it easier to guess which memory map is
faulty.
For the simple case of allowing a device_delegate member to be
configured, use a member like this:
template <typename... T> void set_foo(T &&...args) { m_foo_cb.set(std::forward<T>(args)...); }
For a case where different delegates need to be used depending on the
function signature, see src/emu/screen.h (the screen update function
setters).
Device delegates now take a target specification and function pointer.
The target may be:
* Target omitted, implying the current device being configured. This
can only be used during configuration. It will work as long as the
current device is not removed/replaced.
* A tag string relative to the current device being configured. This
can only be used during configuration. It will not be callable until
.resolve() is called. It will work as long as the current device is
not removed/replaced.
* A device finder (required_device/optional_device). The delegate will
late bind to the current target of the device finder. It will not
be callable until .resolve() is called. It will work properly if the
target device is replaced, as long as the device finder's base object
isn't removed/replaced.
* A reference to an object. It will be callable immediately. It will
work as long as the target object is not removed/replaced.
The target types and restrictions are pretty similar to what you already
have on object finders and devcb, so it shouldn't cause any surprises.
Note that dereferencing a device finder will changes the effect. To
illustrate this:
...
required_device<some_device> m_dev;
...
m_dev(*this, "dev")
...
// will late bind to "dev" relative to *this
// will work if "dev" hasn't been created yet or is replaced later
// won't work if *this is removed/replaced
// won't be callable until resolve() is called
cb1.set(m_dev, FUNC(some_device::w));
...
// will bind to current target of m_dev
// will not work if m_dev is not resolved
// will not work if "dev" is replaced later
// will be callable immediately
cb2.set(*m_dev, FUNC(some_device::w));
...
The order of the target and name has been reversed for functoids
(lambdas and other callable objects). This allows the NAME macro to
be used on lambdas and functoids. For example:
foo.set_something(NAME([this] (u8 data) { m_something = data; }));
I realise the diagnostic messages get ugly if you use NAME on a large
lambda. You can still give a literal name, you just have to place it
after the lambda rather than before. This is uglier, but it's
intentional. I'm trying to drive developers away from a certain style.
While it's nice that you can put half the driver code in the memory map,
it detracts from readability. It's hard to visualise the memory range
mappings if the memory map functions are punctuated by large lambdas.
There's also slightly higher overhead for calling a delegate bound to a
functoid.
If the code is prettier for trivial lambdas but uglier for non-trivial
lambdas in address maps, it will hopefully steer people away from
putting non-trivial lambdas in memory maps.
There were some devices that were converted from using plain delegates
without adding bind_relative_to calls. I fixed some of them (e.g.
LaserDisc) but I probably missed some. These will likely crash on
unresolved delegate calls.
There are some devices that reset delegates at configuration complete or
start time, preventing them from being set up during configuration (e.g.
src/devices/video/ppu2c0x.cpp and src/devices/machine/68307.cpp). This
goes against the design principles of how device delegates should be
used, but I didn't change them because I don't trust myself to find all
the places they're used.
I've definitely broken some stuff with this (I know about asterix), so
report issues and bear with me until I get it all fixed.
A standard memory handler has as a prototype (where uX = u8, u16, u32 or u64):
uX device::read(address_space &space, offs_t offset, uX mem_mask);
void device::write(address_space &space, offs_t offset, uX data, uX mem_mask);
We now allow simplified versions which are:
uX device::read(offs_t offset, uX mem_mask);
void device::write(offs_t offset, uX data, uX mem_mask);
uX device::read(offs_t offset);
void device::write(offs_t offset, uX data);
uX device::read();
void device::write(uX data);
Use them at will. Also consider
(DECLARE_)(READ|WRITE)(8|16|32|64)_MEMBER on the way out, use the
explicit prototypes.
Same for lambdas in the memory map, the parameters are now optional
following the same combinations.
The new cswidth address map constructor method overrides the masking normally performed on narrow-width accesses. This entailed a lot of reconfiguration to make the shifting and masking of subunits independent operations. There is unlikely to have any significant performance impact on drivers that don't frequently reconfigure their memory handlers.
* 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.
In the past few hours AM_DEVREADWRITE_RSHIFT was created. This has made a lot of MAMEdevs (well, mostly R. Belmont) very angry and has widely been regarded as a bad move.
This patch replaces AM_DEVREADWRITE_RSHIFT with a similar but more functional-looking interface (now known as AM_DEVREADWRITE_MOD), which also now supports address line inversion as well as shifting.
These new macros make it easy to map devices addressed using higher address lines (which on actual HW helps to reduce loads on the lowest address lines) without needing to set up custom handlers and associated device finders. The implementation should not impact the efficiency of the core memory system (which Olivier Galibert is trying to improve) since the semantic details are contained within C++11 lambdas.
* move rarely-used output and pty interfaces out of emu.h
* consolidate and de-duplicate forward declarations, also remove some obsolete ones
* clean up more #include guard macros
* scope down a few more things
(nw) Everyone, please keep forward declarations for src/emu in src/emu/emufwd.h -
this will make it far easier to keep them in sync with declarations than having
them scattered through all the other files.
* New abbreviated types are in osd and util namespaces, and also in global namespace for things that #include "emu.h"
* Get rid of import of cstdint types to global namespace (C99 does this anyway)
* Remove the cstdint types from everything in emu
* Get rid of U64/S64 macros
* Fix a bug in dps16 caused by incorrect use of macro
* Fix debugcon not checking for "do " prefix case-insensitively
* Fix a lot of messed up tabulation
* More constexpr
* Fix up many __names
Use standard uint64_t, uint32_t, uint16_t or uint8_t instead of UINT64, UINT32, UINT16 or UINT8
also use standard int64_t, int32_t, int16_t or int8_t instead of INT64, INT32, INT16 or INT8
- Added AM_SELECT/addrselect field. Replaces the old
AM_MIRROR/AM_MASK combo used to mirror a handler and get the mirrored
bits in the offset.
- Removed mask and/or mirror from where it didn't belong. Simplified
a lot of instances of mask that just weren't needed, especially in bus
handlers. Used the short forms of install handlers where possible.
- Replaced the 60s hippy, "It's cool man" range parameter handling in
map_range that tried to guess what was meant when the values passed
were not entirely sensible, by a cranky, diner waitress-turned IRS
auditor curmudgeon. Main control function has a series of 14 tests
just to find a reason to fatalerror out your requests. You have
been warned.
Some drivers, hopefully not many, will fail the gate-guarding
bureaucrat trials. Should be easy to fix actually, I worked on the
error messages. A full regression test would be welcome.
- Eliminate the cached device_t::m_region pointer and its region() getter method. Devices that need to bind to a region with the same tag should use optional/required_memory_region or optional/required_region_ptr with DEVICE_SELF as the subtag; this improves error checking. (DEVICE_SELF has been moved to device.h for greater visibility in the source.)
- Allow required/optional_region_ptr to specify a specific length which must match that of the region found.
- Implement finder_base::finder_tag() getter for diagnostic purposes.
- Perform some (not very efficient) validity checks on memory region finders instead of allowing them to automatically pass.
- Privatize device_memory_interface::m_addrspace.
Explicit regions in address maps (AM_REGION) are now looked up relative to the
device rather than as siblings when in an internal address map (similar to
devices and shared pointers) Besides being more orthogonal than before, this
allows internal ROMs of MCUs and similar devices to be hooked up in a nicer
and more foolproof way. Updated the m37710 and m5074x (m6502 derivative)
to take advantage of this.
Divided the M37702/M37710 into specific models, with each model having its own
internal address map containing the correct amounts of internal RAM and ROM.
M37702 MCUs found on various Namco PCBs are now all unique devices and have
their respective internal ROMs loaded as device ROMs.
(nw)
Also did some spring (fall) cleaning in addrmap.c/memory.c/dimemory.c
m_devbase (the base device used for tagmap lookup when late-binding handlers and
finding memory regions and shares) is now a reference rather than a pointer,
since we know what it is when the address_map_entry is constructed and it
doesn't change (it depends solely on whether it's an entry in an MCFG-provided
address map or an internal one) And for the same reason, there's now only one
m_devbase per address_map_entry rather than individual copies for
read/write/setoffset/sharedptr.
Removed mysterious unused address_map_entry member "m_region_string", along
with a silly assert probably left over from when Aaron was replacing AM_BASE
with AM_SHARE years ago.
Added a comment noting that "make sure all devices exist" in
device_memory_interface::interface_validity_check() actually does nothing,
like the proverbial goggles. The reason there's just a comment and not a fix
is I haven't figured out how to fix it yet
(is it possible to extract the original device tag that was given to a
proto-delegate? Sorry, the template hell in devdelegate.h and
lib/util/delegate.h makes me want to run screaming like a little girl)
Added macros to facilitate declaring gfxdecode info arrays as members
of a device class.
AM_SHAREs in a device's internal address map or its default address map are
now tagmapped as children of that device rather than siblings (analogous
to how handlers in internal/default address maps are scoped).
Converted the Namco C45 to device_gfx_interface.