summaryrefslogtreecommitdiffstatshomepage
path: root/docs
diff options
context:
space:
mode:
author Olivier Galibert <galibert@pobox.com>2020-11-02 12:11:43 +0100
committer Olivier Galibert <galibert@pobox.com>2020-11-02 12:12:11 +0100
commitb8c338858a101d14308c64c87b0f714db4f05326 (patch)
treea34dca21d603b98f5450d31868ce33a7e4c103b0 /docs
parentf4172ded3ec6f3b877ab813f6453d6d1907c1c00 (diff)
emumem: Simplify memory management. [O. Galibert]
API impact: - install_ram/rom/writeonly now requires a non-null pointer. If you want automatically managed ram, add it to a memory map, not in machine_start - install_*_bank now requires a memory_bank *, not a string - one can create memory banks outside of memory maps with memory_bank_creator - one can create memory shares outside of memory maps with memory_share_creator Memory maps impact: - ram ranges with overlapping addresses are not shared anymore. Use .share() - ram ranges touching each other are not merged anymore. Stay in your range Extra note: - there is no need to create a bank just to dynamically map some memory/rom. Just use install_rom/ram/writeonly
Diffstat (limited to 'docs')
-rw-r--r--docs/source/techspecs/index.rst1
-rw-r--r--docs/source/techspecs/memory.rst603
2 files changed, 604 insertions, 0 deletions
diff --git a/docs/source/techspecs/index.rst b/docs/source/techspecs/index.rst
index baec3992140..0d7ddc8ce41 100644
--- a/docs/source/techspecs/index.rst
+++ b/docs/source/techspecs/index.rst
@@ -11,6 +11,7 @@ This section covers technical specifications useful to programmers working on MA
device_memory_interface
device_rom_interface
device_disasm_interface
+ memory
floppy
nscsi
luaengine
diff --git a/docs/source/techspecs/memory.rst b/docs/source/techspecs/memory.rst
new file mode 100644
index 00000000000..e760b004894
--- /dev/null
+++ b/docs/source/techspecs/memory.rst
@@ -0,0 +1,603 @@
+Emulated system memory and address spaces management
+====================================================
+
+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
+programatically.
+
+
+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.
+
+3. Memory objects
+-----------------
+
+3.1 Shares - memory_share
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+| 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.
+
+| 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 memory_share
+object.
+
+| 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 share()
+method to get th memory_share object and the bytes(), endianness(),
+bitwidth() and bytewidth() methods for share information.
+
+| 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
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+| 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
+allows to set a relationship between an entry number and a base
+pointer. configure_entries does the same for multiple consecutive
+entries spanning a memory zone. Alternatively set_base sets the base
+for entry 0 and selects it.
+
+set_entry allows to dynamically and efficiently select the current
+active entry, entry() gets that selection back, and base() gets the
+assotiated base pointer.
+
+| 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.
+
+| memory_bank_creator m_bank;
+|
+| [device constructor] m_bank(\*this, "name"),
+
+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.
+
+| memory_bank \*membank(string tag) const;
+
+The memshare device method retrieves a memory share by name. Beware
+that the lookup can be expensive, prefer finders instead.
+
+
+3.3 Regions - memory_region
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+| class memory_bank {
+| 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 readonly 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.
+
+| 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.
+
+| memory_region \*memregion(string tag) const;
+
+The memshare device method retrieves a memory share by name. Beware
+that the lookup can be expensive, prefer finders instead.
+
+
+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:
+
+| map(start, end).handler(...).handler_qualifier(...).range_qualifier();
+
+The values start and end define the range, the handler() block defines
+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, byte
+selection...).
+
+The map follows a "last one wins" principle, where the last one is
+selected when multiple handlers match a given address.
+
+
+4.2 Global configurations
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+4.2.1 Global masking
+''''''''''''''''''''
+
+| map.global_mask(offs_t mask);
+
+Allows to indicates 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
+''''''''''''''''''''''''''''''''''''''''''''
+
+| 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
+''''''''''''''''''''''''''''''''''
+
+| (...).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
+less wide than the bus itself (for instance a 8-bits device on a
+32-bits 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 drives 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
+''''''''''''''''''''''''''''''''''
+
+| (...).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 a finder
+(required_device or optional_device) or its tag, to read, write or
+both for the current entry.
+
+
+4.3.3 Lambda function
+'''''''''''''''''''''
+
+| (...).lr{8,16,32,64}(FUNC([...](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}(FUNC([...](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}(FUNC(read), FUNC(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 6 available for methods. One can either use FUNC() 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
+''''''''''''''''''''''''''
+
+| (...).rom()
+| (...).writeonly()
+| (...).ram()
+
+Selects the range to access a memory zone as readonly, writeonly or
+readwrite respectively. Specific handle qualifiers allow to tell
+where this memory zone should be. There are two cases when no
+qualifier is acceptable:
+
+* ram() gives an anonymous ram zone not accessibles 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.
+
+| (...).rom().region("name", offset)
+
+The region qualifier allows to make a readonly zone point to the
+contents of a given region at a given offset.
+
+| (...).rom().share("name")
+| (...).writeonly.share("name")
+| (...).ram().share("name")
+
+The share qualifier allow to make the zone point to a shared memory
+region defined by its name. If the region is present in multiple
+spaces the size, the bus width and if the bus is more than byte-wide
+the endianness must match.
+
+
+4.3.5 Bank access
+'''''''''''''''''
+
+| (...).bankr("name")
+| (...).bankw("name")
+| (...).bankrw("name")
+
+Sets the range to point at the contents of a bank is read, write or
+readwrite mode.
+
+
+4.3.6 Port access
+'''''''''''''''''
+
+| (...).portr("name")
+| (...).portw("name")
+| (...).portrw("name")
+
+Sets the range to point at an i/o port.
+
+
+4.3.7 Dropped access
+''''''''''''''''''''
+
+| (...).nopr()
+| (...).nopw()
+| (...).noprw()
+
+Sets the range to drop the access without logging. When reading, the
+unmap value is returned.
+
+
+4.3.8 Unmapped access
+'''''''''''''''''''''
+
+| (...).unmapr()
+| (...).unmapw()
+| (...).unmaprw()
+
+Sets the range to drop the access with logging. When reading, the
+unmap value is returned.
+
+
+4.3.9 Subdevice mapping
+'''''''''''''''''''''''
+
+| (...).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
+'''''''''''''''
+
+| (...).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 mask 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.
+
+
+4.4.2 Masking
+'''''''''''''
+
+| (...).mask(mask)
+
+Only valid with handlers, the address will be masked with the mask
+before being passed to the handler.
+
+
+4.4.3 Selection
+'''''''''''''''
+
+| (...).select(mask)
+
+Only valid with handlers, the range will be mirrored as with mirror,
+but the mirror address bits are kept 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
+''''''''''''''''''''''''
+
+| (...).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 actual
+device with 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
+''''''''''''''''''''''''''''''''''''''
+
+| (...).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 allows to tell 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).
+
+
+5. Address space dynamic mapping API
+------------------------------------
+
+5.1 General API structure
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+A series of methods allow to change the bus decoding of an address
+space on the fly. They're powerful but have some issues:
+* changing the mappings repeateadly 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 that 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 puts them all together as method
+parameters. To make things a little more readable lots of them are
+optional though, the optional ones being written in italics.
+
+
+5.2 Handler mapping
+~~~~~~~~~~~~~~~~~~~
+| 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 12
+types, for read and for write and for all six possible prototypes.
+Note that as all delegates they can also wrap lambdas.
+
+| space.install_read_handler(addrstart, addrend, read_delegate, *unitmask*, *cswidth*)
+| space.install_read_handler(addrstart, addrend, addrmask, addrmirror, addrselect, read_delegate, *unitmask*, *cswidth*)
+| space.install_write_handler(addrstart, addrend, write_delegate, *unitmask*, *cswidth*)
+| space.install_write_handler(addrstart, addrend, addrmask, addrmirror, addrselect, write_delegate, *unitmask*, *cswidth*)
+| space.install_readwrite_handler(addrstart, addrend, read_delegate, write_delegate, *unitmask*, *cswidth*)
+| space.install_readwrite_handler(addrstart, addrend, addrmask, addrmirror, addrselect, read_delegate, write_delegate, *unitmask*, *cswidth*)
+
+These six methods allow to install delegate-wrapped handlers in a live
+address space. either plain of with mask, mirror and select. In the
+readwrite case both delegates must be of the same flavor (smo stuff)
+to avoid a combinatorial explosion of method types.
+
+5.3 Direct memory range mapping
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+| space.install_rom(addrstart, addrend, void \*pointer)
+| space.install_rom(addrstart, addrend, addrmirror, void \*pointer)
+| space.install_writeonly(addrstart, addrend, void \*pointer)
+| space.install_writeonly(addrstart, addrend, addrmirror, void \*pointer)
+| space.install_ram(addrstart, addrend, void \*pointer)
+| space.install_ram(addrstart, addrend, addrmirror, void \*pointer)
+
+Installs a memory block in an address space, with or without mirror.
+rom is readonly, 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
+~~~~~~~~~~~~~~~~
+| space.install_read_bank(addrstart, addrend, memory_bank \*bank)
+| space.install_read_bank(addrstart, addrend, addrmirror, memory_bank \*bank)
+| space.install_write_bank(addrstart, addrend, memory_bank \*bank)
+| space.install_write_bank(addrstart, addrend, addrmirror, memory_bank \*bank)
+| space.install_readwrite_bank(addrstart, addrend, memory_bank \*bank)
+| space.install_readwrite_bank(addrstart, addrend, addrmirror, memory_bank \*bank)
+
+Install for reading, writing or both an existing memory bank in an
+address space.
+
+5.5 Port mapping
+~~~~~~~~~~~~~~~~
+| space.install_read_port(addrstart, addrend, const char \*rtag)
+| space.install_read_port(addrstart, addrend, addrmirror, const char \*rtag)
+| space.install_write_port(addrstart, addrend, const char \*wtag)
+| space.install_write_port(addrstart, addrend, addrmirror, 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)
+
+Install read, write or both ports by name.
+
+5.6 Dropped accesses
+~~~~~~~~~~~~~~~~~~~~
+| space.nop_read(addrstart, addrend, *addrmirror*)
+| space.nop_write(addrstart, addrend, *addrmirror*)
+| space.nop_readwrite(addrstart, addrend, *addrmirror*)
+
+Drops the accesses for a given range with an optional mirror.
+
+5.7 Unmapped accesses
+~~~~~~~~~~~~~~~~~~~~~
+| space.unmap_read(addrstart, addrend, *addrmirror*)
+| space.unmap_write(addrstart, addrend, *addrmirror*)
+| space.unmap_readwrite(addrstart, addrend, *addrmirror*)
+
+Unmaps the accesses (e.g. logs the access as unmapped) for a given
+range with an optional mirror.
+
+5.8 Device map installation
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+| space.install_device(addrstart, addrend, device, map, *unitmask*, *cswidth*)
+
+Install a device address with an address map in a space.