mirror of
https://github.com/holub/mame
synced 2025-04-22 16:31:49 +03:00
965 lines
34 KiB
ReStructuredText
965 lines
34 KiB
ReStructuredText
Emulated system memory and address spaces management
|
||
====================================================
|
||
|
||
.. contents:: :local:
|
||
|
||
|
||
1. Overview
|
||
-----------
|
||
|
||
The memory subsystem (emumem and addrmap) combines multiple functions
|
||
useful for system emulation:
|
||
|
||
* address bus decoding and dispatching with caching
|
||
* static descriptions of an address map
|
||
* RAM allocation and registration for state saving
|
||
* interaction with memory regions to access ROM
|
||
|
||
Devices create address spaces, e.g. decodable buses, through the
|
||
``device_memory_interface``. The machine configuration sets up address
|
||
maps to put in the address spaces, then the device can do read and
|
||
writes through the bus.
|
||
|
||
2. Basic concepts
|
||
-----------------
|
||
|
||
2.1 Address spaces
|
||
~~~~~~~~~~~~~~~~~~
|
||
|
||
An address space, implemented in the class **address_space**,
|
||
represents an addressable bus with potentially multiple sub-devices
|
||
connected requiring a decode. It has a number of data lines (8, 16,
|
||
32 or 64) called data width, a number of address lines (1 to 32)
|
||
called address width and an Endianness. In addition an address shift
|
||
allows for buses that have an atomic granularity different than a
|
||
byte.
|
||
|
||
Address space objects provide a series of methods for read and write
|
||
access, and a second series of methods for dynamically changing the
|
||
decode.
|
||
|
||
|
||
2.2 Address maps
|
||
~~~~~~~~~~~~~~~~
|
||
|
||
An address map is a static description of the decode expected when
|
||
using a bus. It connects to memory, other devices and methods, and is
|
||
installed, usually at startup, in an address space. That description
|
||
is stored in an **address_map** structure which is filled
|
||
programmatically.
|
||
|
||
|
||
2.3 Shares, banks and regions
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
Memory shares are allocated memory zones that can be put in multiple
|
||
places in the same or different address spaces, and can also be
|
||
directly accessed from devices.
|
||
|
||
Memory banks are zones that indirect memory access, giving the
|
||
possibility to dynamically and efficiently change where a zone
|
||
actually points to.
|
||
|
||
Memory regions are read-only memory zones in which ROMs are loaded.
|
||
|
||
All of these have names allowing to access them.
|
||
|
||
2.4 Views
|
||
~~~~~~~~~
|
||
|
||
Views are a way to multiplex different submaps over a memory range
|
||
with fast switching. It is to be used when multiple devices map at
|
||
the same addresses and are switched in externally. They must be
|
||
created as an object of the device and then setup either statically in
|
||
a memory map or dynamically through ``install_*`` calls.
|
||
|
||
Switchable submaps, aka variants, are named through an integer. An
|
||
internal indirection through a map ensures that any integer value can
|
||
be used.
|
||
|
||
|
||
3. Memory objects
|
||
-----------------
|
||
|
||
3.1 Shares - memory_share
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
class memory_share {
|
||
const std::string &name() const;
|
||
void *ptr() const;
|
||
size_t bytes() const;
|
||
endianness_t endianness() const;
|
||
u8 bitwidth() const;
|
||
u8 bytewidth() const;
|
||
};
|
||
|
||
A memory share is a named allocated memory zone that is automatically
|
||
saved in save states and can be mapped in address spaces. It is the
|
||
standard container for memory that is shared between spaces, but also
|
||
shared between an emulated CPU and a driver. As such one has easy
|
||
access to its contents from the driver class.
|
||
|
||
.. code-block:: C++
|
||
|
||
required_shared_ptr<uNN> m_share_ptr;
|
||
optional_shared_ptr<uNN> m_share_ptr;
|
||
required_shared_ptr_array<uNN, count> m_share_ptr_array;
|
||
optional_shared_ptr_array<uNN, count> m_share_ptr_array;
|
||
|
||
[device constructor] m_share_ptr(*this, "name"),
|
||
[device constructor] m_share_ptr_array(*this, "name%u", 0U),
|
||
|
||
At the device level, a pointer to the memory zone can easily be
|
||
retrieved by building one of these four finders. Note that like for
|
||
every finder calling ``target()`` on the finder gives you the base
|
||
pointer of the ``memory_share`` object.
|
||
|
||
.. code-block:: C++
|
||
|
||
memory_share_creator<uNN> m_share;
|
||
|
||
[device constructor] m_share(*this, "name", size, endianness),
|
||
|
||
A memory share can be created if it doesn’t exist in a memory map
|
||
through that creator class. If it already exists it is just
|
||
retrieved. That class behaves like a pointer but also has the
|
||
``target()``, ``length()``, ``bytes()``, ``endianness()``,
|
||
``bitwidth()`` and ``bytewidth()`` methods for share information. The
|
||
desired size is specified in bytes.
|
||
|
||
.. code-block:: C++
|
||
|
||
memory_share *memshare(string tag) const;
|
||
|
||
The ``memshare`` device method retrieves a memory share by name. Beware
|
||
that the lookup can be expensive, prefer finders instead.
|
||
|
||
3.2 Banks - memory_bank
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
class memory_bank {
|
||
const std::string &tag() const;
|
||
int entry() const;
|
||
void set_entry(int entrynum);
|
||
void configure_entry(int entrynum, void *base);
|
||
void configure_entries(int startentry, int numentry, void *base, offs_t stride);
|
||
void set_base(void *base);
|
||
void *base() const;
|
||
};
|
||
|
||
A memory bank is a named memory zone indirection that can be mapped in
|
||
address spaces. It points to ``nullptr`` when created.
|
||
``configure_entry`` associates an entry number and a base pointer.
|
||
``configure_entries`` does the same for multiple consecutive entries
|
||
spanning a memory zone.
|
||
|
||
``set_base`` sets the base address for the active entry. If there are
|
||
no entries, entry 0 (zero) is automatically created and selected. Use
|
||
of ``set_base`` should be avoided in favour of pre-configured entries
|
||
unless there are an impractically large number of possible base
|
||
addresses.
|
||
|
||
``set_entry`` dynamically and efficiently selects the active entry,
|
||
``entry()`` returns the active entry number, and ``base()`` gets the
|
||
associated base pointer.
|
||
|
||
.. code-block:: C++
|
||
|
||
required_memory_bank m_bank;
|
||
optional_memory_bank m_bank;
|
||
required_memory_bank_array<count> m_bank_array;
|
||
optional_memory_bank_array<count> m_bank_array;
|
||
|
||
[device constructor] m_bank(*this, "name"),
|
||
[device constructor] m_bank_array(*this, "name%u", 0U),
|
||
|
||
At the device level, a pointer to the memory bank object can easily be
|
||
retrieved by building one of these four finders.
|
||
|
||
.. code-block:: C++
|
||
|
||
memory_bank_creator m_bank;
|
||
|
||
[device constructor] m_bank(*this, "name"),
|
||
|
||
A memory bank can be created if it doesn’t exist in a memory map
|
||
through that creator class. If it already exists it is just
|
||
retrieved.
|
||
|
||
.. code-block:: C++
|
||
|
||
memory_bank *membank(string tag) const;
|
||
|
||
The ``membank`` device method retrieves a memory bank by name. Beware
|
||
that the lookup can be expensive, prefer finders instead.
|
||
|
||
|
||
3.3 Regions - memory_region
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
class memory_region {
|
||
u8 *base();
|
||
u8 *end();
|
||
u32 bytes() const;
|
||
const std::string &name() const;
|
||
endianness_t endianness() const;
|
||
u8 bitwidth() const;
|
||
u8 bytewidth() const;
|
||
u8 &as_u8(offs_t offset = 0);
|
||
u16 &as_u16(offs_t offset = 0);
|
||
u32 &as_u32(offs_t offset = 0);
|
||
u64 &as_u64(offs_t offset = 0);
|
||
}
|
||
|
||
A region is used to store read-only data like ROMs or the result of
|
||
fixed decryptions. Their contents are not saved, which is why they
|
||
should not being written to from the emulated system. They don’t
|
||
really have an intrinsic width (``base()`` returns an ``u8 *`` always),
|
||
which is historical and pretty much unfixable at this point. The
|
||
``as_*`` methods allow for accessing them at a given width.
|
||
|
||
.. code-block:: C++
|
||
|
||
required_memory_region m_region;
|
||
optional_memory_region m_region;
|
||
required_memory_region_array<count> m_region_array;
|
||
optional_memory_region_array<count> m_region_array;
|
||
|
||
[device constructor] m_region(*this, "name"),
|
||
[device constructor] m_region_array(*this, "name%u", 0U),
|
||
|
||
At the device level, a pointer to the memory region object can easily be
|
||
retrieved by building one of these four finders.
|
||
|
||
.. code-block:: C++
|
||
|
||
memory_region *memregion(string tag) const;
|
||
|
||
The ``memregion`` device method retrieves a memory region by name.
|
||
Beware that the lookup can be expensive, prefer finders instead.
|
||
|
||
|
||
3.4 Views - memory_view
|
||
~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
class memory_view {
|
||
memory_view(device_t &device, std::string name);
|
||
memory_view_entry &operator[](int slot);
|
||
|
||
void select(int entry);
|
||
void disable();
|
||
|
||
const std::string &name() const;
|
||
}
|
||
|
||
A view allows to switch part of a memory map between multiple
|
||
possibilities, or even disable it entirely to see what was there
|
||
before. It is created as an object of the device.
|
||
|
||
.. code-block:: C++
|
||
|
||
memory_view m_view;
|
||
|
||
[device constructor] m_view(*this, "name"),
|
||
|
||
It is then setup through the address map API or dynamically. At
|
||
runtime, a numbered variant can be selected using the ``select`` method,
|
||
or the view can be disabled using the ``disable`` method. A disabled
|
||
view can be re-enabled at any time.
|
||
|
||
|
||
.. _3.5:
|
||
|
||
3.5 Bus contention handling
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
Some specific CPUs have been upgraded to be interruptible which allows
|
||
to add bus contention and wait states capabitilites. Being
|
||
interruptible means, in practice, that an instruction can be
|
||
interrupted at any time and the execute_run method of the core exited.
|
||
Other devices can then run, then eventually controls returns to the
|
||
core and the instruction continues from the point it was started.
|
||
Importantly, this can be triggered from a handler and even be used to
|
||
interrupt just before the access that is currently done
|
||
(e.g. continuation will redo the access).
|
||
|
||
The CPUs supporting that declare their capability by overriding the
|
||
method ``cpu_is_interruptible`` to return true.
|
||
|
||
Three intermediate contention handlers can be added to accesses:
|
||
|
||
* ``before_delay``: wait a number of cycles before doing the access.
|
||
* ``after_delay``: wait a number of cycles after doing the access.
|
||
* ``before_time``: wait for a given time before doing the access.
|
||
|
||
For the delay handlers, a method or lambda is called which returns the
|
||
number of cycles to wait (as a u32).
|
||
|
||
The ``before_time`` is special. First, the time is compared to the
|
||
current value of cpu->total_cycles(). That value is the number of
|
||
cycles elapsed since the last reset of the cpu. It is passed as a
|
||
parameter to the method as a u64 and must return the earliest time as
|
||
a u64 when the access can be done, which can be equal to the passed-in
|
||
time. From there two things can happen: either the running cpu has
|
||
enough cycles left to consume to reach that time. In that case, the
|
||
necessary number of cycles is consumed, and the access is done.
|
||
Otherwise, when there isn't enough, the remaining cycles are consumed,
|
||
the access aborted, scheduling happens, and eventually the access is
|
||
redone. In that case the method is called again with the new current
|
||
time, and must return the (probably same) earliest time again. This
|
||
will happen until enough cycles to consume are available to directly
|
||
do the access.
|
||
|
||
This approach allows to for instance handle consecutive DMAs. A first
|
||
DMA grabs the bus for a transfer. This shows up as the method
|
||
answering for the earliest time for access the time of the end of the
|
||
dma. If no timer happens until that time the access will then happen
|
||
just after the dma finishes. But if a timer elapses before that and
|
||
as a consequence another dma is queued while the first is running, the
|
||
cycle will be aborted for lack of remaining time, and the method will
|
||
eventually be called again. It will then give the time of when the
|
||
second dma will finish, and all will be well.
|
||
|
||
It can also allow to reduce said earlier time when circumstances
|
||
require it. For instance a PIO latch that waits up to 64 cycles that
|
||
data arrives can indicate that current time + 64 as a target (which
|
||
will trigger a bus error for instance) but if a timer elapses and
|
||
fills the latch meanwhile the method will be called again and that
|
||
time can just return the current time to let the access pass though.
|
||
Beware that if the timer elapsing did not fill the latch then the
|
||
method must return the time it returned previously, e.g. the initial
|
||
access time + 64, otherwise irrelevant timers happening or simply
|
||
scheduling quantum effects will delay the timeout, possibly to
|
||
infinity if the quantum is small enough.
|
||
|
||
Contention handlers on the same address are taken into account in the
|
||
``before_time``, ``before_delay`` then ``after_delay`` order.
|
||
Contention handlers of the same type on the same address at
|
||
last-one-wins. Installing any non-contention handler on a range where
|
||
a contention handler was removes it.
|
||
|
||
|
||
4. Address maps API
|
||
-------------------
|
||
|
||
4.1 General API structure
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
An address map is a method of a device which fills an **address_map**
|
||
structure, usually called **map**, passed by reference. The method
|
||
then can set some global configuration through specific methods and
|
||
then provide address range-oriented entries which indicate what should
|
||
happen when a specific range is accessed.
|
||
|
||
The general syntax for entries uses method chaining:
|
||
|
||
.. code-block:: C++
|
||
|
||
map(start, end).handler(...).handler_qualifier(...).range_qualifier().contention();
|
||
|
||
The values start and end define the range, the handler() block
|
||
determines how the access is handled, the handler_qualifier() block
|
||
specifies some aspects of the handler (memory sharing for instance)
|
||
and the range_qualifier() block refines the range (mirroring, masking,
|
||
lane selection, etc.). The contention methods handle bus contention
|
||
and wait states for cpus supporting them.
|
||
|
||
The map follows a “last one wins” principle, where the handler specified
|
||
last is selected when multiple handlers match a given address.
|
||
|
||
|
||
4.2 Global configurations
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
4.2.1 Global masking
|
||
''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
map.global_mask(offs_t mask);
|
||
|
||
Specifies a mask to be applied to all addresses when accessing the space
|
||
that map is installed in.
|
||
|
||
|
||
4.2.2 Returned value on unmapped/nop-ed read
|
||
''''''''''''''''''''''''''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
map.unmap_value_low();
|
||
map.unmap_value_high();
|
||
map.unmap_value(u8 value);
|
||
|
||
Sets the value to return on reads to an unmapped or nopped-out address.
|
||
Low means 0, high ~0.
|
||
|
||
|
||
4.3 Handler setting
|
||
~~~~~~~~~~~~~~~~~~~
|
||
|
||
4.3.1 Method on the current device
|
||
''''''''''''''''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).r(FUNC(my_device::read_method))
|
||
(...).w(FUNC(my_device::write_method))
|
||
(...).rw(FUNC(my_device::read_method), FUNC(my_device::write_method))
|
||
|
||
uNN my_device::read_method(address_space &space, offs_t offset, uNN mem_mask)
|
||
uNN my_device::read_method(address_space &space, offs_t offset)
|
||
uNN my_device::read_method(address_space &space)
|
||
uNN my_device::read_method(offs_t offset, uNN mem_mask)
|
||
uNN my_device::read_method(offs_t offset)
|
||
uNN my_device::read_method()
|
||
|
||
void my_device::write_method(address_space &space, offs_t offset, uNN data, uNN mem_mask)
|
||
void my_device::write_method(address_space &space, offs_t offset, uNN data)
|
||
void my_device::write_method(address_space &space, uNN data)
|
||
void my_device::write_method(offs_t offset, uNN data, uNN mem_mask)
|
||
void my_device::write_method(offs_t offset, uNN data)
|
||
void my_device::write_method(uNN data)
|
||
|
||
Sets a method of the current device or driver to read, write or both
|
||
for the current entry. The prototype of the method can take multiple
|
||
forms making some elements optional. ``uNN`` represents ``u8``,
|
||
``u16``, ``u32`` or ``u64`` depending on the data width of the handler.
|
||
The handler can be narrower than the bus itself (for instance an 8-bit
|
||
device on a 32-bit bus).
|
||
|
||
The offset passed in is built from the access address. It starts at
|
||
zero at the start of the range, and increments for each ``uNN`` unit.
|
||
An ``u8`` handler will get an offset in bytes, an ``u32`` one in double
|
||
words. The ``mem_mask`` has its bits set where the accessors actually
|
||
drive the bit. It’s usually built in byte units, but in some cases of
|
||
I/O chips ports with per-bit direction registers the resolution can be
|
||
at the bit level.
|
||
|
||
|
||
4.3.2 Method on a different device
|
||
''''''''''''''''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).r(m_other_device, FUNC(other_device::read_method))
|
||
(...).r("other-device-tag", FUNC(other_device::read_method))
|
||
(...).w(m_other_device, FUNC(other_device::write_method))
|
||
(...).w("other-device-tag", FUNC(other_device::write_method))
|
||
(...).rw(m_other_device, FUNC(other_device::read_method), FUNC(other_device::write_method))
|
||
(...).rw("other-device-tag", FUNC(other_device::read_method), FUNC(other_device::write_method))
|
||
|
||
Sets a method of another device, designated by an object finder
|
||
(usually ``required_device`` or ``optional_device``) or its tag, to
|
||
read, write or both for the current entry.
|
||
|
||
|
||
4.3.3 Lambda function
|
||
'''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).lr{8,16,32,64}(NAME([...](address_space &space, offs_t offset, uNN mem_mask) -> uNN { ... }))
|
||
(...).lr{8,16,32,64}([...](address_space &space, offs_t offset, uNN mem_mask) -> uNN { ... }, "name")
|
||
(...).lw{8,16,32,64}(NAME([...](address_space &space, offs_t offset, uNN data, uNN mem_mask) -> void { ... }))
|
||
(...).lw{8,16,32,64}([...](address_space &space, offs_t offset, uNN data, uNN mem_mask) -> void { ... }, "name")
|
||
(...).lrw{8,16,32,64}(NAME(read), NAME(write))
|
||
(...).lrw{8,16,32,64}(read, "name_r", write, "name_w")
|
||
|
||
Sets a lambda called on read, write or both. The lambda prototype can
|
||
be any of the six available for methods. One can either use ``NAME()``
|
||
over the whole lambda, or provide a name after the lambda definition.
|
||
The number is the data width of the access, e.g. the NN.
|
||
|
||
|
||
4.3.4 Direct memory access
|
||
''''''''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).rom()
|
||
(...).writeonly()
|
||
(...).ram()
|
||
|
||
Selects the range to access a memory zone as read-only, write-only or
|
||
read/write respectively. Specific handler qualifiers specify the
|
||
location of this memory zone. There are two cases when no qualifier is
|
||
acceptable:
|
||
|
||
* ``ram()`` gives an anonymous RAM zone not accessible outside of the
|
||
address space.
|
||
|
||
* ``rom()`` when the memory map is used in an ``AS_PROGRAM``
|
||
space of a (CPU) device which names is also the name of a region.
|
||
Then the memory zone points to that region at the offset
|
||
corresponding to the start of the zone.
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).rom().region("name", offset)
|
||
|
||
The ``region`` qualifier causes a read-only zone point to the contents
|
||
of a given region at a given offset.
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).rom().share("name")
|
||
(...).writeonly.share("name")
|
||
(...).ram().share("name")
|
||
|
||
The ``share`` qualifier causes the zone point to a shared memory region
|
||
identified by its name. If the share is present in multiple spaces, the
|
||
size, bus width, and, if the bus is more than byte-wide, the Endianness
|
||
must match.
|
||
|
||
|
||
4.3.5 Bank access
|
||
'''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).bankr("name")
|
||
(...).bankw("name")
|
||
(...).bankrw("name")
|
||
|
||
Sets the range to point at the contents of a memory bank in read, write
|
||
or read/write mode.
|
||
|
||
|
||
4.3.6 Port access
|
||
'''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).portr("name")
|
||
(...).portw("name")
|
||
(...).portrw("name")
|
||
|
||
Sets the range to point at an I/O port.
|
||
|
||
|
||
4.3.7 Dropped access
|
||
''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).nopr()
|
||
(...).nopw()
|
||
(...).noprw()
|
||
|
||
Sets the range to drop the access without logging. When reading, the
|
||
unmap value is returned.
|
||
|
||
|
||
4.3.8 Unmapped access
|
||
'''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).unmapr()
|
||
(...).unmapw()
|
||
(...).unmaprw()
|
||
|
||
Sets the range to drop the access with logging. When reading, the
|
||
unmap value is returned.
|
||
|
||
|
||
4.3.9 Subdevice mapping
|
||
'''''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).m(m_other_device, FUNC(other_device::map_method))
|
||
(...).m("other-device-tag", FUNC(other_device::map_method))
|
||
|
||
Includes a device-defined submap. The start of the range indicates
|
||
where the address zero of the submap ends up, and the end of the range
|
||
clips the submap if needed. Note that range qualifiers (defined
|
||
later) apply.
|
||
|
||
Currently, only handlers are allowed in submaps and not memory zones
|
||
or banks.
|
||
|
||
|
||
4.4 Range qualifiers
|
||
~~~~~~~~~~~~~~~~~~~~
|
||
|
||
4.4.1 Mirroring
|
||
'''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).mirror(mask)
|
||
|
||
Duplicate the range on the addresses reachable by setting any of the 1
|
||
bits present in mask. For instance, a range 0-0x1f with mirror 0x300
|
||
will be present on 0-0x1f, 0x100-0x11f, 0x200-0x21f and 0x300-0x31f.
|
||
The addresses passed in to the handler stay in the 0-0x1f range, the
|
||
mirror bits are not seen by the handler.
|
||
|
||
|
||
4.4.2 Masking
|
||
'''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).mask(mask)
|
||
|
||
Only valid with handlers, the address will be masked with the mask
|
||
before being passed to the handler.
|
||
|
||
|
||
4.4.3 Selection
|
||
'''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).select(mask)
|
||
|
||
Only valid with handlers, the range will be mirrored as with mirror,
|
||
but the mirror address bits are preserved in the offset passed to the
|
||
handler when it is called. This is useful for devices like sound
|
||
chips where the low bits of the address select a function and the high
|
||
bits a voice number.
|
||
|
||
|
||
4.4.4 Sub-unit selection
|
||
''''''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).umask16(16-bits mask)
|
||
(...).umask32(32-bits mask)
|
||
(...).umask64(64-bits mask)
|
||
|
||
Only valid with handlers and submaps, selects which data lines of the
|
||
bus are actually connected to the handler or the device. The mask value
|
||
should be a multiple of a byte, e.g. the mask is a series of 00 and ff.
|
||
The offset will be adjusted accordingly, so that a difference of 1 means
|
||
the next handled unit in the access.
|
||
|
||
If the mask is narrower than the bus width, the mask is replicated in
|
||
the upper lines.
|
||
|
||
|
||
4.4.5 Chip select handling on sub-unit
|
||
''''''''''''''''''''''''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).cselect(16/32/64)
|
||
|
||
When a device is connected to part of the bus, like a byte on a
|
||
16-bits bus, the target handler is only activated when that part is
|
||
actually accessed. In some cases, very often byte access on a 68000
|
||
16-bits bus, the actual hardware only checks the word address and not
|
||
if the correct byte is accessed. ``cswidth`` tells the memory system to
|
||
trigger the handler if a wider part of the bus is accessed. The
|
||
parameter is that trigger width (would be 16 in the 68000 case).
|
||
|
||
|
||
4.4.6 User flags
|
||
''''''''''''''''
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).flags(16-bits mask)
|
||
|
||
This parameter allows to set user-defined flags on the handler which
|
||
can then be retrieved by an accessing device to change their
|
||
behaviour. An example of use the i960 which marks burstable zones
|
||
that way (they have a specific hardware-level support).
|
||
|
||
|
||
4.5 Contention
|
||
~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
(...).before_time(method).(...)
|
||
(...).before_delay(method).(...)
|
||
(...).after_delay(method).(...)
|
||
|
||
These three methods allow to add the contention methods to a handler.
|
||
See section `3.5`_. Multiple methods can be handler to one handler.
|
||
|
||
|
||
4.6 View setup
|
||
~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
map(start, end).view(m_view);
|
||
m_view[0](start1, end1).[...];
|
||
|
||
A view is setup in a address map with the view method. The only
|
||
qualifier accepted is mirror. The “disabled” version of the view will
|
||
include what was in the range prior to the view setup.
|
||
|
||
The different variants are setup by indexing the view with the variant
|
||
number and setting up an entry in the usual way. The entries within a
|
||
variant must of course stay within the range. There are no other
|
||
additional constraints. The contents of a variant, by default, are
|
||
what was there before, i.e. the contents of the disabled view, and
|
||
setting it up allows part or all of it to be overridden.
|
||
|
||
Variants can only be setup once the view itself has been setup with
|
||
the ``view`` method.
|
||
|
||
A view can only be put in one address map and in only one position.
|
||
If multiple views have identical or similar contents, remember that
|
||
setting up a map is nothing more than a method call, and creating a
|
||
second method to setup a view is perfectly reasonable. A view is of
|
||
type ``memory_view`` and an indexed entry (e.g. a variant to setup) is
|
||
of type ``memory_view::memory_view_entry &``.
|
||
|
||
A view can be installed in another view, but don’t forget that a view
|
||
can be installed only once. A view can also be part of “what was there
|
||
before”.
|
||
|
||
|
||
|
||
5. Address space dynamic mapping API
|
||
------------------------------------
|
||
|
||
5.1 General API structure
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
A series of methods allow the bus decoding of an address space to be
|
||
changed on-the-fly. They’re powerful but have some issues:
|
||
|
||
* changing the mappings repeatedly can be slow
|
||
* the address space state is not saved in the saved states, so it has to
|
||
be rebuilt after state load
|
||
* they can be hidden anywhere rather than be grouped in an address map,
|
||
which can be less readable
|
||
|
||
The methods, rather than decomposing the information in handler, handler
|
||
qualifier and range qualifier, put them all together as method
|
||
parameters. To make things a little more readable, lots of them are
|
||
optional.
|
||
|
||
|
||
5.2 Handler mapping
|
||
~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
uNN my_device::read_method(address_space &space, offs_t offset, uNN mem_mask)
|
||
uNN my_device::read_method_m(address_space &space, offs_t offset)
|
||
uNN my_device::read_method_mo(address_space &space)
|
||
uNN my_device::read_method_s(offs_t offset, uNN mem_mask)
|
||
uNN my_device::read_method_sm(offs_t offset)
|
||
uNN my_device::read_method_smo()
|
||
|
||
void my_device::write_method(address_space &space, offs_t offset, uNN data, uNN mem_mask)
|
||
void my_device::write_method_m(address_space &space, offs_t offset, uNN data)
|
||
void my_device::write_method_mo(address_space &space, uNN data)
|
||
void my_device::write_method_s(offs_t offset, uNN data, uNN mem_mask)
|
||
void my_device::write_method_sm(offs_t offset, uNN data)
|
||
void my_device::write_method_smo(uNN data)
|
||
|
||
readNN_delegate (device, FUNC(read_method))
|
||
readNNm_delegate (device, FUNC(read_method_m))
|
||
readNNmo_delegate (device, FUNC(read_method_mo))
|
||
readNNs_delegate (device, FUNC(read_method_s))
|
||
readNNsm_delegate (device, FUNC(read_method_sm))
|
||
readNNsmo_delegate(device, FUNC(read_method_smo))
|
||
|
||
writeNN_delegate (device, FUNC(write_method))
|
||
writeNNm_delegate (device, FUNC(write_method_m))
|
||
writeNNmo_delegate (device, FUNC(write_method_mo))
|
||
writeNNs_delegate (device, FUNC(write_method_s))
|
||
writeNNsm_delegate (device, FUNC(write_method_sm))
|
||
writeNNsmo_delegate(device, FUNC(write_method_smo))
|
||
|
||
To be added to a map, a method call and the device it is called onto
|
||
have to be wrapped in the appropriate delegate type. There are twelve
|
||
types, for read and for write and for all six possible prototypes.
|
||
Note that as all delegates, they can also wrap lambdas.
|
||
|
||
.. code-block:: C++
|
||
|
||
space.install_read_handler(addrstart, addrend, read_delegate, unitmask, cswidth, flags)
|
||
space.install_read_handler(addrstart, addrend, addrmask, addrmirror, addrselect, read_delegate, unitmask, cswidth, flags)
|
||
space.install_write_handler(addrstart, addrend, write_delegate, unitmask, cswidth, flags)
|
||
space.install_write_handler(addrstart, addrend, addrmask, addrmirror, addrselect, write_delegate, unitmask, cswidth, flags)
|
||
space.install_readwrite_handler(addrstart, addrend, read_delegate, write_delegate, unitmask, cswidth, flags)
|
||
space.install_readwrite_handler(addrstart, addrend, addrmask, addrmirror, addrselect, read_delegate, write_delegate, unitmask, cswidth, flags)
|
||
|
||
These six methods allow to install delegate-wrapped handlers in a live
|
||
address space. Either plain or with mask, mirror and select. In the
|
||
read/write case both delegates must be of the same flavor (``smo``
|
||
stuff) to avoid a combinatorial explosion of method types. The
|
||
``unitmask``, ``cswidth`` and ``flags`` arguments are optional.
|
||
|
||
5.3 Direct memory range mapping
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
space.install_rom(addrstart, addrend, void *pointer)
|
||
space.install_rom(addrstart, addrend, addrmirror, void *pointer)
|
||
space.install_rom(addrstart, addrend, addrmirror, flags, void *pointer)
|
||
space.install_writeonly(addrstart, addrend, void *pointer)
|
||
space.install_writeonly(addrstart, addrend, addrmirror, void *pointer)
|
||
space.install_writeonly(addrstart, addrend, addrmirror, flags, void *pointer)
|
||
space.install_ram(addrstart, addrend, void *pointer)
|
||
space.install_ram(addrstart, addrend, addrmirror, void *pointer)
|
||
space.install_ram(addrstart, addrend, addrmirror, flags, void *pointer)
|
||
|
||
Installs a memory block in an address space, with or without mirror
|
||
and flags. ``_rom`` is read-only, ``_ram`` is read/write,
|
||
``_writeonly`` is write-only. The pointer must be non-null, this
|
||
method will not allocate the memory.
|
||
|
||
5.4 Bank mapping
|
||
~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
space.install_read_bank(addrstart, addrend, memory_bank *bank)
|
||
space.install_read_bank(addrstart, addrend, addrmirror, memory_bank *bank)
|
||
space.install_read_bank(addrstart, addrend, addrmirror, flags, memory_bank *bank)
|
||
space.install_write_bank(addrstart, addrend, memory_bank *bank)
|
||
space.install_write_bank(addrstart, addrend, addrmirror, memory_bank *bank)
|
||
space.install_write_bank(addrstart, addrend, addrmirror, flags, memory_bank *bank)
|
||
space.install_readwrite_bank(addrstart, addrend, memory_bank *bank)
|
||
space.install_readwrite_bank(addrstart, addrend, addrmirror, memory_bank *bank)
|
||
space.install_readwrite_bank(addrstart, addrend, addrmirror, flags, memory_bank *bank)
|
||
|
||
Install an existing memory bank for reading, writing or both in an
|
||
address space.
|
||
|
||
5.5 Port mapping
|
||
~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
space.install_read_port(addrstart, addrend, const char *rtag)
|
||
space.install_read_port(addrstart, addrend, addrmirror, const char *rtag)
|
||
space.install_read_port(addrstart, addrend, addrmirror, flags, const char *rtag)
|
||
space.install_write_port(addrstart, addrend, const char *wtag)
|
||
space.install_write_port(addrstart, addrend, addrmirror, const char *wtag)
|
||
space.install_write_port(addrstart, addrend, addrmirror, flags, const char *wtag)
|
||
space.install_readwrite_port(addrstart, addrend, const char *rtag, const char *wtag)
|
||
space.install_readwrite_port(addrstart, addrend, addrmirror, const char *rtag, const char *wtag)
|
||
space.install_readwrite_port(addrstart, addrend, addrmirror, flags, const char *rtag, const char *wtag)
|
||
|
||
Install ports by name for reading, writing or both.
|
||
|
||
5.6 Dropped accesses
|
||
~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
space.nop_read(addrstart, addrend, addrmirror, flags)
|
||
space.nop_write(addrstart, addrend, addrmirror, flags)
|
||
space.nop_readwrite(addrstart, addrend, addrmirror, flags)
|
||
|
||
Drops the accesses for a given range with an optional mirror and flags;
|
||
|
||
5.7 Unmapped accesses
|
||
~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
space.unmap_read(addrstart, addrend, addrmirror, flags)
|
||
space.unmap_write(addrstart, addrend, addrmirror, flags)
|
||
space.unmap_readwrite(addrstart, addrend, addrmirror, flags)
|
||
|
||
Unmaps the accesses (e.g. logs the access as unmapped) for a given range
|
||
with an optional mirror and flags.
|
||
|
||
5.8 Device map installation
|
||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
space.install_device(addrstart, addrend, device, map, unitmask, cswidth, flags)
|
||
|
||
Install a device address with an address map in a space. The
|
||
``unitmask``, ``cswidth`` and ``flags`` arguments are optional.
|
||
|
||
5.9 Contention
|
||
~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
using ws_time_delegate = device_delegate<u64 (offs_t, u64)>;
|
||
using ws_delay_delegate = device_delegate<u32 (offs_t)>;
|
||
|
||
space.install_read_before_time(addrstart, addrend, addrmirror, ws_time_delegate)
|
||
space.install_write_before_time(addrstart, addrend, addrmirror, ws_time_delegate)
|
||
space.install_readwrite_before_time(addrstart, addrend, addrmirror, ws_time_delegate)
|
||
|
||
space.install_read_before_delay(addrstart, addrend, addrmirror, ws_delay_delegate)
|
||
space.install_write_before_delay(addrstart, addrend, addrmirror, ws_delay_delegate)
|
||
space.install_readwrite_before_delay(addrstart, addrend, addrmirror, ws_delay_delegate)
|
||
|
||
space.install_read_after_delay(addrstart, addrend, addrmirror, ws_delay_delegate)
|
||
space.install_write_after_delay(addrstart, addrend, addrmirror, ws_delay_delegate)
|
||
space.install_readwrite_after_delay(addrstart, addrend, addrmirror, ws_delay_delegate)
|
||
|
||
Install a contention handler in the decode path. The addrmirror
|
||
parameter is optional.
|
||
|
||
|
||
5.10 View installation
|
||
~~~~~~~~~~~~~~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
space.install_view(addrstart, addrend, view)
|
||
space.install_view(addrstart, addrend, addrmirror, view)
|
||
|
||
view[0].install...
|
||
|
||
Installs a view in a space. This can be only done once and in only
|
||
one space, and the view must not have been setup through the address
|
||
map API before. Once the view is installed, variants can be selected
|
||
by indexing to call a dynamic mapping method on it.
|
||
|
||
A view can be installed into a variant of another view without issues,
|
||
with only the usual constraint of single installation.
|
||
|
||
5.11 Taps
|
||
~~~~~~~~~
|
||
|
||
.. code-block:: C++
|
||
|
||
using tap = std::function<void (offs_t offset, uNN &data, uNN mem_mask)
|
||
|
||
memory_passthrough_handler mph = space.install_read_tap(addrstart, addrend, name, read_tap, &mph);
|
||
memory_passthrough_handler mph = space.install_write_tap(addrstart, addrend, name, write_tap, &mph);
|
||
memory_passthrough_handler mph = space.install_readwrite_tap(addrstart, addrend, name, read_tap, write_tap, &mph);
|
||
|
||
mph.remove();
|
||
|
||
A tap is a method that is be called when a specific range of addresses
|
||
is accessed without overriding the actual access. Taps can change the
|
||
data passed around. A write tap happens before the access, and can
|
||
change the value to be written. A read tap happens after the access,
|
||
and can change the value returned.
|
||
|
||
Taps must be of the same width and alignement than the bus. Multiple
|
||
taps can act over the same addresses.
|
||
|
||
The ``memory_passthrough_handler`` object collates a number of taps
|
||
and allow to remove them all in one call. The ``mph`` parameter is
|
||
optional and a new one will be created if absent.
|
||
|
||
Taps are lost when a new handler is installed at the same addresses
|
||
(under the usual principle of last one wins). If they need to be
|
||
preserved, one should install a change notifier on the address space,
|
||
and remove + reinstall the taps when notified.
|
||
|