diff options
Diffstat (limited to 'docs/source/techspecs/object_finders.rst')
-rw-r--r-- | docs/source/techspecs/object_finders.rst | 1039 |
1 files changed, 1039 insertions, 0 deletions
diff --git a/docs/source/techspecs/object_finders.rst b/docs/source/techspecs/object_finders.rst new file mode 100644 index 00000000000..27fe42fdd50 --- /dev/null +++ b/docs/source/techspecs/object_finders.rst @@ -0,0 +1,1039 @@ +Object Finders +============== + +.. contents:: :local: + + +Introduction +------------ + +Object finders are an important part of the glue MAME provides to tie the +devices that make up an emulated system together. Object finders are used to +specify connections between devices, to efficiently access resources, and to +check that necessary resources are available on validation. + +Object finders search for a target object by tag relative to a base device. +Some types of object finder require additional parameters. + +Most object finders have required and optional versions. The required versions +will raise an error if the target object is not found. This will prevent a +device from starting or cause a validation error. The optional versions will +log a verbose message if the target object is not found, and provide additional +members for testing whether the target object was found or not. + +Object finder classes are declared in the header src/emu/devfind.h and have +Doxygen format API documentation. + + +Types of object finder +---------------------- + +required_device<DeviceClass>, optional_device<DeviceClass> + Finds a device. The template argument ``DeviceClass`` should be a class + derived from ``device_t`` or ``device_interface``. +required_memory_region, optional_memory_region + Finds a memory region, usually from ROM definitions. The target is the + ``memory_region`` object. +required_memory_bank, optional_memory_bank + Finds a memory bank instantiated in an address map. The target is the + ``memory_bank`` object. +memory_bank_creator + Finds a memory bank instantiated in an address map, or creates it if it + doesn’t exist. The target is the ``memory_bank`` object. There is no + optional version, because the target object will always be found or + created. +required_ioport, optional_ioport + Finds an I/O port from a device’s input port definitions. The target is the + ``ioport_port`` object. +required_address_space, optional_address_space + Finds a device’s address space. The target is the ``address_space`` object. +required_region_ptr<PointerType>, optional_region_ptr<PointerType> + Finds the base pointer of a memory region, usually from ROM definitions. + The template argument ``PointerType`` is the target type (usually an + unsigned integer type). The target is the first element in the memory + region. +required_shared_ptr<PointerType>, optional_shared_ptr<PointerType> + Finds the base pointer of a memory share instantiated in an address map. + The template argument ``PointerType`` is the target type (usually an + unsigned integer type). The target is the first element in the memory + share. +memory_share_creator<PointerType> + Finds the base pointer of a memory share instantiated in an address map, or + creates it if it doesn’t exist. The template argument ``PointerType`` is + the target type (usually an unsigned integer type). The target is the first + element in the memory share. There is no optional version, because the + target object will always be found or created. + + +Finding resources +----------------- + +We’ll start with a simple example of a device that uses object finders to access +its own child devices, inputs and ROM region. The code samples here are based +on the Apple II Parallel Printer Interface card, but a lot of things have been +removed for clarity. + +Object finders are declared as members of the device class: + +.. code-block:: C++ + + class a2bus_parprn_device : public device_t, public device_a2bus_card_interface + { + public: + a2bus_parprn_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock); + + virtual void write_c0nx(u8 offset, u8 data) override; + virtual u8 read_cnxx(u8 offset) override; + + protected: + virtual tiny_rom_entry const *device_rom_region() const override; + virtual void device_add_mconfig(machine_config &config) override; + virtual ioport_constructor device_input_ports() const override; + + private: + required_device<centronics_device> m_printer_conn; + required_device<output_latch_device> m_printer_out; + required_ioport m_input_config; + required_region_ptr<u8> m_prom; + }; + +We want to find a ``centronics_device``, an ``output_latch_device``, an I/O +port, and an 8-bit memory region. + +In the constructor, we set the initial target for the object finders: + +.. code-block:: C++ + + a2bus_parprn_device::a2bus_parprn_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, A2BUS_PARPRN, tag, owner, clock), + device_a2bus_card_interface(mconfig, *this), + m_printer_conn(*this, "prn"), + m_printer_out(*this, "prn_out"), + m_input_config(*this, "CFG"), + m_prom(*this, "prom") + { + } + +Each object finder takes a base device and tag as constructor arguments. The +base device supplied at construction serves two purposes. Most obviously, the +tag is specified relative to this device. Possibly more importantly, the object +finder registers itself with this device so that it will be called to perform +validation and object resolution. + +Note that the object finders *do not* copy the tag strings. The caller must +ensure the tag string remains valid until after validation and/or object +resolution is complete. + +The memory region and I/O port come from the ROM definition and input +definition, respectively: + +.. code-block:: C++ + + namespace { + + ROM_START(parprn) + ROM_REGION(0x100, "prom", 0) + ROM_LOAD( "prom.b4", 0x0000, 0x0100, BAD_DUMP CRC(00b742ca) SHA1(c67888354aa013f9cb882eeeed924e292734e717) ) + ROM_END + + INPUT_PORTS_START(parprn) + PORT_START("CFG") + PORT_CONFNAME(0x01, 0x00, "Acknowledge latching edge") + PORT_CONFSETTING( 0x00, "Falling (/Y-B)") + PORT_CONFSETTING( 0x01, "Rising (Y-B)") + PORT_CONFNAME(0x06, 0x02, "Printer ready") + PORT_CONFSETTING( 0x00, "Always (S5-C-D)") + PORT_CONFSETTING( 0x02, "Acknowledge latch (Z-C-D)") + PORT_CONFSETTING( 0x04, "ACK (Y-C-D)") + PORT_CONFSETTING( 0x06, "/ACK (/Y-C-D)") + PORT_CONFNAME(0x08, 0x00, "Strobe polarity") + PORT_CONFSETTING( 0x00, "Negative (S5-A-/X, GND-X)") + PORT_CONFSETTING( 0x08, "Positive (S5-X, GND-A-/X)") + PORT_CONFNAME(0x10, 0x10, "Character width") + PORT_CONFSETTING( 0x00, "7-bit") + PORT_CONFSETTING( 0x10, "8-bit") + INPUT_PORTS_END + + } // anonymous namespace + + tiny_rom_entry const *a2bus_parprn_device::device_rom_region() const + { + return ROM_NAME(parprn); + } + + ioport_constructor a2bus_parprn_device::device_input_ports() const + { + return INPUT_PORTS_NAME(parprn); + } + +Note that the tags ``"prom"`` and ``"CFG"`` match the tags passed to the object +finders on construction. + +Child devices are instantiated in the device’s machine configuration member +function: + +.. code-block:: C++ + + void a2bus_parprn_device::device_add_mconfig(machine_config &config) + { + CENTRONICS(config, m_printer_conn, centronics_devices, "printer"); + m_printer_conn->ack_handler().set(FUNC(a2bus_parprn_device::ack_w)); + + OUTPUT_LATCH(config, m_printer_out); + m_printer_conn->set_output_latch(*m_printer_out); + } + +Object finders are passed to device types to provide tags when instantiating +child devices. After instantiating a child device in this way, the object +finder can be used like a pointer to the device until the end of the machine +configuration member function. Note that to use an object finder like this, +its base device must be the same as the device being configured (the ``this`` +pointer of the machine configuration member function). + +After the emulated machine has been started, the object finders can be used in +much the same way as pointers: + +.. code-block:: C++ + + void a2bus_parprn_device::write_c0nx(u8 offset, u8 data) + { + ioport_value const cfg(m_input_config->read()); + + m_printer_out->write(data & (BIT(cfg, 8) ? 0xffU : 0x7fU)); + m_printer_conn->write_strobe(BIT(~cfg, 3)); + } + + + u8 a2bus_parprn_device::read_cnxx(u8 offset) + { + offset ^= 0x40U; + return m_prom[offset]; + } + +For convenience, object finders that target the base pointer of memory regions +and shares can be indexed like arrays. + + +Connections between devices +--------------------------- + +Devices need to be connected together within a system. For example the Sun SBus +device needs access to the host CPU and address space. Here’s how we declare +the object finders in the device class (with all distractions removed): + +.. code-block:: C++ + + DECLARE_DEVICE_TYPE(SBUS, sbus_device) + + class sbus_device : public device_t, public device_memory_interface + { + template <typename T, typename U> + sbus_device( + machine_config const &mconfig, char const *tag, device_t *owner, u32 clock, + T &&cpu_tag, + U &&space_tag, int space_num) : + sbus_device(mconfig, tag, owner, clock) + { + set_cpu(std::forward<T>(cpu_tag)); + set_type1space(std::forward<U>(space_tag), space_num); + } + + sbus_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, SBUS, tag, owner, clock), + device_memory_interface(mconfig, *this), + m_maincpu(*this, finder_base::DUMMY_TAG), + m_type1space(*this, finder_base::DUMMY_TAG, -1) + { + } + + template <typename T> void set_cpu(T &&tag) { m_maincpu.set_tag(std::forward<T>(tag)); } + template <typename T> void set_type1space(T &&tag, int num) { m_type1space.set_tag(std::forward<T>(tag), num); } + + protected: + required_device<sparc_base_device> m_maincpu; + required_address_space m_type1space; + }; + +There are several things to take note of here: + +* Object finder members are declared for the things the device needs to access. +* The device doesn’t know how it will fit into a larger system, the object + finders are constructed with dummy arguments. +* Configuration member functions are provided to set the tag for the host CPU, + and the tag and index for the type 1 address space. +* In addition to the standard device constructor, a constructor with additional + parameters for setting the CPU and type 1 address space is provided. + +The constant ``finder_base::DUMMY_TAG`` is guaranteed to be invalid and will not +resolve to an object. This makes it easy to detect incomplete configuration and +report an error. Address spaces are numbered from zero, so a negative address +space number is invalid. + +The member functions for configuring object finders take a universal reference +to a tag-like object (templated type with ``&&`` qualifier), as well as any +other parameters needed by the specific type of object finder. An address space +finder needs an address space number in addition to a tag-like object. + +So what’s a tag-like object? Three things are supported: + +* A C string pointer (``char const *``) representing a tag relative to the + device being configured. Note that the object finder will not copy the + string. The caller must ensure it remains valid until resolution and/or + validation is complete. +* Another object finder. The object finder will take on its current target. +* For device finders, a reference to an instance of the target device type, + setting the target to that device. Note that this will not work if the device + is subsequently replaced in the machine configuration. It’s most often used + with ``*this``. + +The additional constructor that sets initial configuration delegates to the +standard constructor and then calls the configuration member functions. It’s +purely for convenience. + +When we want to instantiate this device and hook it up, we do this:: + + SPARCV7(config, m_maincpu, 20'000'000); + + ADDRESS_MAP_BANK(config, m_type1space); + + SBUS(config, m_sbus, 20'000'000); + m_sbus->set_cpu(m_maincpu); + m_sbus->set_type1space(m_type1space, 0); + +We supply the same object finders to instantiate the CPU and address space +devices, and to configure the SBus device. + +Note that we could also use literal C strings to configure the SBus device, at +the cost of needing to update the tags in multiple places if they change:: + + SBUS(config, m_sbus, 20'000'000); + m_sbus->set_cpu("maincpu"); + m_sbus->set_type1space("type1", 0); + +If we want to use the convenience constructor, we just supply additional +arguments when instantiating the device:: + + SBUS(config, m_sbus, 20'000'000, m_maincpu, m_type1space, 0); + + +Object finder arrays +-------------------- + +Many systems have multiple similar devices, I/O ports or other resources that +can be logically organised as an array. To simplify these use cases, object +finder array types are provided. The object finder array type names have +``_array`` added to them: + ++------------------------+------------------------------+ +| required_device | required_device_array | ++------------------------+------------------------------+ +| optional_device | optional_device_array | ++------------------------+------------------------------+ +| required_memory_region | required_memory_region_array | ++------------------------+------------------------------+ +| optional_memory_region | optional_memory_region_array | ++------------------------+------------------------------+ +| required_memory_bank | required_memory_bank_array | ++------------------------+------------------------------+ +| optional_memory_bank | optional_memory_bank_array | ++------------------------+------------------------------+ +| memory_bank_creator | memory_bank_array_creator | ++------------------------+------------------------------+ +| required_ioport | required_ioport_array | ++------------------------+------------------------------+ +| optional_ioport | optional_ioport_array | ++------------------------+------------------------------+ +| required_address_space | required_address_space_array | ++------------------------+------------------------------+ +| optional_address_space | optional_address_space_array | ++------------------------+------------------------------+ +| required_region_ptr | required_region_ptr_array | ++------------------------+------------------------------+ +| optional_region_ptr | optional_region_ptr_array | ++------------------------+------------------------------+ +| required_shared_ptr | required_shared_ptr_array | ++------------------------+------------------------------+ +| optional_shared_ptr | optional_shared_ptr_array | ++------------------------+------------------------------+ +| memory_share_creator | memory_share_array_creator | ++------------------------+------------------------------+ + +A common case for an object array finder is a key matrix: + +.. code-block:: C++ + + class keyboard_base : public device_t, public device_mac_keyboard_interface + { + protected: + keyboard_base(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, type, tag, owner, clock), + device_mac_keyboard_interface(mconfig, *this), + m_rows(*this, "ROW%u", 0U) + { + } + + u8 bus_r() + { + u8 result(0xffU); + for (unsigned i = 0U; m_rows.size() > i; ++i) + { + if (!BIT(m_row_drive, i)) + result &= m_rows[i]->read(); + } + return result; + } + + required_ioport_array<10> m_rows; + }; + +Constructing an object finder array is similar to constructing an object finder, +except that rather than just a tag you supply a tag format string and index +offset. In this case, the tags of the I/O ports in the array will be ``ROW0``, +``ROW1``, ``ROW2``, … ``ROW9``. Note that the object finder array allocates +dynamic storage for the tags, which remain valid until destruction. + +The object finder array is used in much the same way as a ``std::array`` of the +underlying object finder type. It supports indexing, iterators, and range-based +``for`` loops. + +Because an index offset is specified, the tags don’t need to use zero-based +indices. It’s common to use one-based indexing like this: + +.. code-block:: C++ + + class dooyong_state : public driver_device + { + protected: + dooyong_state(machine_config const &mconfig, device_type type, char const *tag) : + driver_device(mconfig, type, tag), + m_bg(*this, "bg%u", 1U), + m_fg(*this, "fg%u", 1U) + { + } + + optional_device_array<dooyong_rom_tilemap_device, 2> m_bg; + optional_device_array<dooyong_rom_tilemap_device, 2> m_fg; + }; + +This causes ``m_bg`` to find devices with tags ``bg1`` and ``bg2``, while +``m_fg`` finds devices with tags ``fg1`` and ``fg2``. Note that the indexes +into the object finder arrays are still zero-based like any other C array. + +It’s also possible to other format conversions, like hexadecimal (``%x`` and +``%X``) or character (``%c``): + +.. code-block:: C++ + + class eurit_state : public driver_device + { + public: + eurit_state(machine_config const &mconfig, device_type type, char const *tag) : + driver_device(mconfig, type, tag), + m_keys(*this, "KEY%c", 'A') + { + } + + private: + required_ioport_array<5> m_keys; + }; + +In this case, the key matrix ports use tags ``KEYA``, ``KEYB``, ``KEYC``, +``KEYD`` and ``KEYE``. + +When the tags don’t follow a simple ascending sequence, you can supply a +brace-enclosed initialiser list of tags: + +.. code-block:: C++ + + class seabattl_state : public driver_device + { + public: + seabattl_state(machine_config const &mconfig, device_type type, char const *tag) : + driver_device(mconfig, type, tag), + m_digits(*this, { "sc_thousand", "sc_hundred", "sc_half", "sc_unity", "tm_half", "tm_unity" }) + { + } + + private: + required_device_array<dm9368_device, 6> m_digits; + }; + +If the underlying object finders require additional constructor arguments, +supply them after the tag format and index offset (the same values will be used +for all elements of the array): + +.. code-block:: C++ + + class dreamwld_state : public driver_device + { + public: + dreamwld_state(machine_config const &mconfig, device_type type, char const *tag) : + driver_device(mconfig, type, tag), + m_vram(*this, "vram_%u", 0U, 0x2000U, ENDIANNESS_BIG) + { + } + + private: + memory_share_array_creator<u16, 2> m_vram; + }; + +This finds or creates memory shares with tags ``vram_0`` and ``vram_1``, each of +which is 8 KiB organised as 4,096 big-Endian 16-bit words. + + +Optional object finders +----------------------- + +Optional object finders don’t raise an error if the target object isn’t found. +This is useful in two situations: ``driver_device`` implementations (state +classes) representing a family of systems where some components aren’t present +in all configurations, and devices that can optionally use a resource. Optional +object finders provide additional member functions for testing whether the +target object was found. + +Optional system components +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Often a class is used to represent a family of related systems. If a component +isn’t present in all configurations, it may be convenient to use an optional +object finder to access it. We’ll use the Sega X-board device as an example: + +.. code-block:: C++ + + class segaxbd_state : public device_t + { + protected: + segaxbd_state(machine_config const &mconfig, device_type type, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, type, tag, owner, clock), + m_soundcpu(*this, "soundcpu"), + m_soundcpu2(*this, "soundcpu2"), + m_segaic16vid(*this, "segaic16vid"), + m_pc_0(0), + m_lastsurv_mux(0), + m_adc_ports(*this, "ADC%u", 0), + m_mux_ports(*this, "MUX%u", 0) + { + } + + optional_device<z80_device> m_soundcpu; + optional_device<z80_device> m_soundcpu2; + required_device<mb3773_device> m_watchdog; + required_device<segaic16_video_device> m_segaic16vid; + bool m_adc_reverse[8]; + u8 m_pc_0; + u8 m_lastsurv_mux; + optional_ioport_array<8> m_adc_ports; + optional_ioport_array<4> m_mux_ports; + }; + +The ``optional_device`` and ``optional_ioport_array`` members are declared and +constructed in the usual way. Before accessing the target object, we call an +object finder’s ``found()`` member function to check whether it’s present in the +system (the explicit cast-to-Boolean operator can be used for the same purpose): + +.. code-block:: C++ + + void segaxbd_state::pc_0_w(u8 data) + { + m_pc_0 = data; + + m_watchdog->write_line_ck(BIT(data, 6)); + + m_segaic16vid->set_display_enable(data & 0x20); + + if (m_soundcpu.found()) + m_soundcpu->set_input_line(INPUT_LINE_RESET, (data & 0x01) ? CLEAR_LINE : ASSERT_LINE); + if (m_soundcpu2.found()) + m_soundcpu2->set_input_line(INPUT_LINE_RESET, (data & 0x01) ? CLEAR_LINE : ASSERT_LINE); + } + +Optional I/O ports provide a convenience member function called ``read_safe`` +that reads the port value if present, or returns the supplied default value +otherwise: + +.. code-block:: C++ + + u8 segaxbd_state::analog_r() + { + int const which = (m_pc_0 >> 2) & 7; + u8 value = m_adc_ports[which].read_safe(0x10); + + if (m_adc_reverse[which]) + value = 255 - value; + + return value; + } + + u8 segaxbd_state::lastsurv_port_r() + { + return m_mux_ports[m_lastsurv_mux].read_safe(0xff); + } + +The ADC ports return 0x10 (16 decimal) if they are not present, while the +multiplexed digital ports return 0xff (255 decimal) if they are not present. +Note that ``read_safe`` is a member of the ``optional_ioport`` itself, and not +a member of the target ``ioport_port`` object (the ``optional_ioport`` is not +dereferenced when using it). + +There are some disadvantages to using optional object finders: + +* There’s no way to distinguish between the target not being present, and the + target not being found due to mismatched tags, making it more error-prone. +* Checking whether the target is present may use CPU branch prediction + resources, potentially hurting performance if it happens very frequently. + +Consider whether optional object finders are the best solution, or whether +creating a derived class for the system with additional components is more +appropriate. + +Optional resources +~~~~~~~~~~~~~~~~~~ + +Some devices can optionally use certain resources. If the host system doesn’t +supply them, the device will still function, although some functionality may not +be available. For example, the Virtual Boy cartridge slot responds to three +address spaces, called EXP, CHIP and ROM. If the host system will never use one +or more of them, it doesn’t need to supply a place for the cartridge to install +the corresponding handlers. (For example a copier may only use the ROM space.) + +Let’s look at how this is implemented. The Virtual Boy cartridge slot device +declares ``optional_address_space`` members for the three address spaces, +``offs_t`` members for the base addresses in these spaces, and inline member +functions for configuring them: + +.. code-block:: C++ + + class vboy_cart_slot_device : + public device_t, + public device_image_interface, + public device_single_card_slot_interface<device_vboy_cart_interface> + { + public: + vboy_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock = 0U); + + template <typename T> void set_exp(T &&tag, int no, offs_t base) + { + m_exp_space.set_tag(std::forward<T>(tag), no); + m_exp_base = base; + } + template <typename T> void set_chip(T &&tag, int no, offs_t base) + { + m_chip_space.set_tag(std::forward<T>(tag), no); + m_chip_base = base; + } + template <typename T> void set_rom(T &&tag, int no, offs_t base) + { + m_rom_space.set_tag(std::forward<T>(tag), no); + m_rom_base = base; + } + + protected: + virtual void device_start() override; + + private: + optional_address_space m_exp_space; + optional_address_space m_chip_space; + optional_address_space m_rom_space; + offs_t m_exp_base; + offs_t m_chip_base; + offs_t m_rom_base; + + device_vboy_cart_interface *m_cart; + }; + + DECLARE_DEVICE_TYPE(VBOY_CART_SLOT, vboy_cart_slot_device) + +The object finders are constructed with dummy values for the tags and space +numbers (``finder_base::DUMMY_TAG`` and -1): + +.. code-block:: C++ + + vboy_cart_slot_device::vboy_cart_slot_device(machine_config const &mconfig, char const *tag, device_t *owner, u32 clock) : + device_t(mconfig, VBOY_CART_SLOT, tag, owner, clock), + device_image_interface(mconfig, *this), + device_single_card_slot_interface<device_vboy_cart_interface>(mconfig, *this), + m_exp_space(*this, finder_base::DUMMY_TAG, -1, 32), + m_chip_space(*this, finder_base::DUMMY_TAG, -1, 32), + m_rom_space(*this, finder_base::DUMMY_TAG, -1, 32), + m_exp_base(0U), + m_chip_base(0U), + m_rom_base(0U), + m_cart(nullptr) + { + } + +To help detect configuration errors, we’ll check for cases where address spaces +have been configured but aren’t present: + +.. code-block:: C++ + + void vboy_cart_slot_device::device_start() + { + if (!m_exp_space && ((m_exp_space.finder_tag() != finder_base::DUMMY_TAG) || (m_exp_space.spacenum() >= 0))) + throw emu_fatalerror("%s: Address space %d of device %s not found (EXP)\n", tag(), m_exp_space.spacenum(), m_exp_space.finder_tag()); + + if (!m_chip_space && ((m_chip_space.finder_tag() != finder_base::DUMMY_TAG) || (m_chip_space.spacenum() >= 0))) + throw emu_fatalerror("%s: Address space %d of device %s not found (CHIP)\n", tag(), m_chip_space.spacenum(), m_chip_space.finder_tag()); + + if (!m_rom_space && ((m_rom_space.finder_tag() != finder_base::DUMMY_TAG) || (m_rom_space.spacenum() >= 0))) + throw emu_fatalerror("%s: Address space %d of device %s not found (ROM)\n", tag(), m_rom_space.spacenum(), m_rom_space.finder_tag()); + + m_cart = get_card_device(); + } + + +Object finder types in more detail +---------------------------------- + +All object finders provide configuration functionality: + +.. code-block:: C++ + + char const *finder_tag() const { return m_tag; } + std::pair<device_t &, char const *> finder_target(); + void set_tag(device_t &base, char const *tag); + void set_tag(char const *tag); + void set_tag(finder_base const &finder); + +The ``finder_tag`` and ``finder_target`` member function provides access to the +currently configured target. Note that the tag returned by ``finder`` tag is +relative to the base device. It is not sufficient on its own to identify the +target. + +The ``set_tag`` member functions configure the target of the object finder. +These members must not be called after the object finder is resolved. The first +form configures the base device and relative tag. The second form sets the +relative tag and also implicitly sets the base device to the device that is +currently being configured. This form must only be called from machine +configuration functions. The third form sets the base object and relative tag +to the current target of another object finder. + +Note that the ``set_tag`` member functions **do not** copy the relative tag. It +is the caller’s responsibility to ensure the C string remains valid until the +object finder is resolved (or reconfigured with a different tag). The base +device must also be valid at resolution time. This may not be the case if the +device could be removed or replaced later. + +All object finders provide the same interface for accessing the target object: + +.. code-block:: C++ + + ObjectClass *target() const; + operator ObjectClass *() const; + ObjectClass *operator->() const; + +These members all provide access to the target object. The ``target`` member +function and cast-to-pointer operator will return ``nullptr`` if the target has +not been found. The pointer member access operator asserts that the target has +been found. + +Optional object finders additionally provide members for testing whether the +target object has been found: + +.. code-block:: C++ + + bool found() const; + explicit operator bool() const; + +These members return ``true`` if the target was found, on the assumption +that the target pointer will be non-null if the target was found. + +Device finders +~~~~~~~~~~~~~~ + +Device finders require one template argument for the expected device class. +This should derive from either ``device_t`` or ``device_interface``. The target +device object must either be an instance of this class, an instance of a class +that derives from it. A warning message is logged if a matching device is found +but it is not an instance of the expected class. + +Device finders provide an additional ``set_tag`` overload: + +.. code-block:: C++ + + set_tag(DeviceClass &object); + +This is equivalent to calling ``set_tag(object, DEVICE_SELF)``. Note that the +device object must not be removed or replaced before the object finder is +resolved. + +Memory system object finders +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The memory system object finders, ``required_memory_region``, +``optional_memory_region``, ``required_memory_bank``, ``optional_memory_bank`` +and ``memory_bank_creator``, do not have any special functionality. They are +often used in place of literal tags when installing memory banks in an address +space. + +Example using memory bank finders in an address map: + +.. code-block:: C++ + + class qvt70_state : public driver_device + { + public: + qvt70_state(machine_config const &mconfig, device_type type, char const *tag) : + driver_device(mconfig, type, tag), + m_rombank(*this, "rom"), + m_rambank(*this, "ram%d", 0U), + { } + + private: + required_memory_bank m_rombank; + required_memory_bank_array<2> m_rambank; + + void mem_map(address_map &map); + + void rombank_w(u8 data); + }; + + void qvt70_state::mem_map(address_map &map) + { + map(0x0000, 0x7fff).bankr(m_rombank); + map(0x8000, 0x8000).w(FUNC(qvt70_state::rombank_w)); + map(0xa000, 0xbfff).ram(); + map(0xc000, 0xdfff).bankrw(m_rambank[0]); + map(0xe000, 0xffff).bankrw(m_rambank[1]); + } + +Example using a memory bank creator to install a memory bank dynamically: + +.. code-block:: C++ + + class vegaeo_state : public eolith_state + { + public: + vegaeo_state(machine_config const &mconfig, device_type type, char const *tag) : + eolith_state(mconfig, type, tag), + m_qs1000_bank(*this, "qs1000_bank") + { + } + + void init_vegaeo(); + + private: + memory_bank_creator m_qs1000_bank; + }; + + void vegaeo_state::init_vegaeo() + { + // Set up the QS1000 program ROM banking, taking care not to overlap the internal RAM + m_qs1000->cpu().space(AS_IO).install_read_bank(0x0100, 0xffff, m_qs1000_bank); + m_qs1000_bank->configure_entries(0, 8, memregion("qs1000:cpu")->base() + 0x100, 0x10000); + + init_speedup(); + } + +I/O port finders +~~~~~~~~~~~~~~~~ + +Optional I/O port finders provide an additional convenience member function: + +.. code-block:: C++ + + ioport_value read_safe(ioport_value defval); + +This will read the port’s value if the target I/O port was found, or return +``defval`` otherwise. It is useful in situations where certain input devices +are not always present. + + +Address space finders +~~~~~~~~~~~~~~~~~~~~~ + +Address space finders accept an additional argument for the address space number +to find. A required data width can optionally be supplied to the constructor. + +.. code-block:: C++ + + address_space_finder(device_t &base, char const *tag, int spacenum, u8 width = 0); + void set_tag(device_t &base, char const *tag, int spacenum); + void set_tag(char const *tag, int spacenum); + void set_tag(finder_base const &finder, int spacenum); + template <bool R> void set_tag(address_space_finder<R> const &finder); + +The base device and tag must identify a device that implements +``device_memory_interface``. The address space number is a zero-based index to +one of the device’s address spaces. + +If the width is non-zero, it must match the target address space’s data width in +bits. If the target address space exists but has a different data width, a +warning message will be logged, and it will be treated as not being found. If +the width is zero (the default argument value), the target address space’s data +width won’t be checked. + +Member functions are also provided to get the configured address space number +and set the required data width: + +.. code-block:: C++ + + int spacenum() const; + void set_data_width(u8 width); + +Memory pointer finders +~~~~~~~~~~~~~~~~~~~~~~ + +The memory pointer finders, ``required_region_ptr``, ``optional_region_ptr``, +``required_shared_ptr``, ``optional_shared_ptr`` and ``memory_share_creator``, +all require one template argument for the element type of the memory area. This +should usually be an explicitly-sized unsigned integer type (``u8``, ``u16``, +``u32`` or ``u64``). The size of this type is compared to the width of the +memory area. If it doesn’t match, a warning message is logged and the region or +share is treated as not being found. + +The memory pointer finders provide an array access operator, and members for +accessing the size of the memory area: + +.. code-block:: C++ + + PointerType &operator[](int index) const; + size_t length() const; + size_t bytes() const; + +The array access operator returns a non-\ ``const`` reference to an element of +the memory area. The index is in units of the element type; it must be +non-negative and less than the length of the memory area. The ``length`` member +returns the number of elements in the memory area. The ``bytes`` member returns +the size of the memory area in bytes. These members should not be called if the +target region/share has not been found. + +The ``memory_share_creator`` requires additional constructor arguments for the +size and Endianness of the memory share: + +.. code-block:: C++ + + memory_share_creator(device_t &base, char const *tag, size_t bytes, endianness_t endianness); + +The size is specified in bytes. If an existing memory share is found, it is an +error if its size does not match the specified size. If the width is wider than +eight bits and an existing memory share is found, it is an error if its +Endianness does not match the specified Endianness. + +The ``memory_share_creator`` provides additional members for accessing +properties of the memory share: + +.. code-block:: C++ + + endianness_t endianness() const; + u8 bitwidth() const; + u8 bytewidth() const; + +These members return the Endianness, width in bits and width in bytes of the +memory share, respectively. They must not be called if the memory share has not +been found. + + +Output finders +-------------- + +Output finders are used for exposing outputs that can be used by the artwork +system, or by external programs. A common application using an external program +is a control panel or cabinet lighting controller. + +Output finders are not really object finders, but they’re described here because +they’re used in a similar way. There are a number of important differences to +be aware of: + +* Output finders always create outputs if they do not exist. +* Output finders must be manually resolved, they are not automatically resolved. +* Output finders cannot have their target changed after construction. +* Output finders are array-like, and support an arbitrary number of dimensions. +* Output names are global, the base device has no influence. (This will change + in the future.) + +Output finders take a variable number of template arguments corresponding to the +number of array dimensions you want. Let’s look at an example that uses zero-, +one- and two-dimensional output finders: + +.. code-block:: C++ + + class mmd2_state : public driver_device + { + public: + mmd2_state(machine_config const &mconfig, device_type type, char const *tag) : + driver_device(mconfig, type, tag), + m_digits(*this, "digit%u", 0U), + m_p(*this, "p%u_%u", 0U, 0U), + m_led_halt(*this, "led_halt"), + m_led_hold(*this, "led_hold") + { } + + protected: + virtual void machine_start() override; + + private: + void round_leds_w(offs_t, u8); + void digit_w(u8 data); + void status_callback(u8 data); + + u8 m_digit; + + output_finder<9> m_digits; + output_finder<3, 8> m_p; + output_finder<> m_led_halt; + output_finder<> m_led_hold; + }; + +The ``m_led_halt`` and ``m_led_hold`` members are zero-dimensional output +finders. They find a single output each. The ``m_digits`` member is a +one-dimensional output finder. It finds nine outputs organised as a +single-dimensional array. The ``m_p`` member is a two-dimensional output +finder. It finds 24 outputs organised as three rows of eight columns each. +Larger numbers of dimensions are supported. + +The output finder constructor takes a base device reference, a format string, +and an index offset for each dimension. In this case, all the offsets are +zero. The one-dimensional output finder ``m_digits`` will find outputs +``digit0``, ``digit1``, ``digit2``, … ``digit8``. The two-dimensional output +finder ``m_p`` will find the outputs ``p0_0``, ``p0_1``, … ``p0_7`` for the +first row, ``p1_0``, ``p1_1``, … ``p1_7`` for the second row, and ``p2_0``, +``p2_1``, … ``p2_7`` for the third row. + +You must call ``resolve`` on each output finder before it can be used. This +should be done at start time for the output values to be included in save +states: + +.. code-block:: C++ + + void mmd2_state::machine_start() + { + m_digits.resolve(); + m_p.resolve(); + m_led_halt.resolve(); + m_led_hold.resolve(); + + save_item(NAME(m_digit)); + } + +Output finders provide operators allowing them to be assigned from or cast to +32-bit signed integers. The assignment operator will send a notification if the +new value is different to the output’s current value. + +.. code-block:: C++ + + operator s32() const; + s32 operator=(s32 value); + +To set output values, assign through the output finders, as you would with an +array of the same rank: + +.. code-block:: C++ + + void mmd2_state::round_leds_w(offs_t offset, u8 data) + { + for (u8 i = 0; i < 8; i++) + m_p[offset][i] = BIT(~data, i); + } + + void mmd2_state::digit_w(u8 data) + { + if (m_digit < 9) + m_digits[m_digit] = data; + } + + void mmd2_state::status_callback(u8 data) + { + m_led_halt = (~data & i8080_cpu_device::STATUS_HLTA) ? 1 : 0; + m_led_hold = (data & i8080_cpu_device::STATUS_WO) ? 1 : 0; + } |