-emu/devfind.h: Made read_safe avaiable for optional I/O ports only.

-docs: Added the next couple of sections explaining object finders.
This commit is contained in:
Vas Crabb 2020-11-06 03:00:57 +11:00
parent 453aed194b
commit ae8c70b7e6
12 changed files with 308 additions and 49 deletions

View File

@ -43,8 +43,8 @@ memory_bank_creator
optional version, because the target object will always be found or
created.
required_ioport, optional_ioport
Finds and I/O port from a devices input port definitions. The target is
the ``ioport_port`` object.
Finds an I/O port from a devices input port definitions. The target is the
``ioport_port`` object.
required_address_space, optional_address_space
Finds a devices address space. The target is the ``address_space`` object.
required_region_ptr<PointerType>, optional_region_ptr<PointerType>
@ -200,5 +200,238 @@ much the same way as pointers::
return m_prom[offset];
}
For convenience, object finders that target the base addresses of memory regions
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. Heres how we declare
the object finders in the device class (with all distractions removed)::
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, type, 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 doesnt 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 whats 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. Its most often used
with ``*this``.
The additional constructor that sets initial configuration delegates to the
standard constructor and then calls the configuration member functions. Its
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 |
+------------------------+------------------------------+
A common case for an object array finder is a key matrix::
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 dont need to use zero-based
indices. Its common to use one-based indexing like this::
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.
Its also possible to other format conversions, like hexadecimal (``%x`` and
``%X``) or character (``%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 dont follow a simple ascending sequence, you can supply a
brace-enclosed initialiser list of tags::
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;
};

View File

@ -80,7 +80,7 @@ beckerport_device::~beckerport_device()
device_start
-------------------------------------------------*/
void beckerport_device::device_start(void)
void beckerport_device::device_start()
{
char chAddress[64];
@ -104,7 +104,7 @@ void beckerport_device::device_start(void)
device_stop
-------------------------------------------------*/
void beckerport_device::device_stop(void)
void beckerport_device::device_stop()
{
if (m_pSocket)
{
@ -117,7 +117,7 @@ void beckerport_device::device_stop(void)
device_config_complete
-------------------------------------------------*/
void beckerport_device::device_config_complete(void)
void beckerport_device::device_config_complete()
{
m_hostname = "127.0.0.1";
m_dwtcpport = 65504;
@ -198,9 +198,9 @@ void beckerport_device::write(offs_t offset, u8 data)
update_port
-------------------------------------------------*/
void beckerport_device::update_port(void)
void beckerport_device::update_port()
{
device_stop();
m_dwtcpport = m_dwconfigport.read_safe(65504);
m_dwtcpport = m_dwconfigport->read();
device_start();
}

View File

@ -165,14 +165,14 @@ uint16_t md_jcart_device::read(offs_t offset)
if (m_jcart_io_data[0] & 0x40)
{
joy[0] = m_jcart3.read_safe(0);
joy[1] = m_jcart4.read_safe(0);
joy[0] = m_jcart3->read();
joy[1] = m_jcart4->read();
return (m_jcart_io_data[0] & 0x40) | joy[0] | (joy[1] << 8);
}
else
{
joy[0] = ((m_jcart3.read_safe(0) & 0xc0) >> 2) | (m_jcart3.read_safe(0) & 0x03);
joy[1] = ((m_jcart4.read_safe(0) & 0xc0) >> 2) | (m_jcart4.read_safe(0) & 0x03);
joy[0] = ((m_jcart3->read() & 0xc0) >> 2) | (m_jcart3->read() & 0x03);
joy[1] = ((m_jcart4->read() & 0xc0) >> 2) | (m_jcart4->read() & 0x03);
return (m_jcart_io_data[0] & 0x40) | joy[0] | (joy[1] << 8);
}
}
@ -208,14 +208,14 @@ uint16_t md_seprom_codemast_device::read(offs_t offset)
if (m_jcart_io_data[0] & 0x40)
{
joy[0] = m_jcart3.read_safe(0);
joy[1] = m_jcart4.read_safe(0);
joy[0] = m_jcart3->read();
joy[1] = m_jcart4->read();
return (m_jcart_io_data[0] & 0x40) | joy[0] | (joy[1] << 8);
}
else
{
joy[0] = ((m_jcart3.read_safe(0) & 0xc0) >> 2) | (m_jcart3.read_safe(0) & 0x03);
joy[1] = ((m_jcart4.read_safe(0) & 0xc0) >> 2) | (m_jcart4.read_safe(0) & 0x03);
joy[0] = ((m_jcart3->read() & 0xc0) >> 2) | (m_jcart3->read() & 0x03);
joy[1] = ((m_jcart4->read() & 0xc0) >> 2) | (m_jcart4->read() & 0x03);
return (m_jcart_io_data[0] & 0x40) | joy[0] | (joy[1] << 8);
}
}

View File

@ -281,7 +281,7 @@ uint8_t mtx_sdx_device::sdx_status_r()
uint8_t data = 0x00;
data |= m_dsw[BIT(m_control, 0)].read_safe(0x0f) & 0x0f;
data |= m_dsw[BIT(m_control, 0)]->read() & 0x0f;
data |= (m_floppy0->get_device() && m_floppy1->get_device()) ? 0x10 : 0x00;

View File

@ -85,7 +85,7 @@ sbus_device::sbus_device(const machine_config &mconfig, device_type type, const
, device_memory_interface(mconfig, *this)
, m_space_config("SBus Space", ENDIANNESS_BIG, 32, 32, 0, address_map_constructor())
, m_maincpu(*this, finder_base::DUMMY_TAG)
, m_type1space(*this, finder_base::DUMMY_TAG)
, m_type1space(*this, finder_base::DUMMY_TAG, -1)
, m_irq_cb(*this)
, m_buserr(*this)
{

View File

@ -58,20 +58,18 @@ class sbus_device : public device_t,
public:
// construction/destruction
template <typename T, typename U>
sbus_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&cpu_tag, U &&space_tag)
sbus_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock, T &&cpu_tag, U &&space_tag, int space_num)
: sbus_device(mconfig, tag, owner, clock)
{
set_cpu_tag(std::forward<T>(cpu_tag));
set_space_tag(std::forward<U>(space_tag));
set_cpu(std::forward<T>(cpu_tag));
set_type1space(std::forward<U>(space_tag), space_num);
}
sbus_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock);
// inline configuration
template <typename T> void set_cpu_tag(T &&tag) { m_maincpu.set_tag(std::forward<T>(tag)); }
template <typename T> void set_space_tag(T &&tag) { m_type1space.set_tag(std::forward<T>(tag)); }
// devcb3
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); }
template <unsigned Line> auto irq() { return m_irq_cb[Line].bind(); }
auto buserr() { return m_buserr.bind(); }
@ -101,7 +99,7 @@ protected:
// internal state
required_device<sparc_base_device> m_maincpu;
required_device<address_map_bank_device> m_type1space;
required_address_space m_type1space;
address_space *m_space;
devcb_write_line::array<7> m_irq_cb;

View File

@ -836,6 +836,44 @@ protected:
template <unsigned Count> using memory_bank_array_creator = object_array_finder<memory_bank_creator, Count>;
/// \brief Base class for optional I/O port finders
///
/// Adds helpers for to test whether the I/O port was found, and to read
/// the port value or return a default if the I/O port was not found.
template <>
class object_finder_base<ioport_port, false> : public object_finder_common_base<ioport_port, false>
{
public:
/// \brief Read I/O port if found or return default value
///
/// If the I/O port was found, this reads a value from the I/O port
/// and returns it. If the I/O port was not found, the default
/// value (supplied as a parameter) is returned.
/// \param [in] defval Value to return if I/O port was not found.
/// \return Value read from I/O port if found, or supplied default
/// value otherwise.
ioport_value read_safe(ioport_value defval) { return this->m_target ? this->m_target->read() : defval; }
/// \brief Return whether target has been found
///
/// Works on the assumption that the target object pointer will
/// be non-null if the target has been found, and null
/// otherwise.
/// \return True if object has been found, or false otherwise.
bool found() const { return this->m_target != nullptr; }
/// \brief Cast-to-Boolean operator
///
/// Allows truth tests to test whether it's safe to dereference
/// the object, similar to pointers and various C++ standard
/// library objects.
/// \return True if safe to dereference, or false otherwise.
explicit operator bool() const { return this->m_target != nullptr; }
using object_finder_common_base<ioport_port, false>::object_finder_common_base;
};
/// \brief I/O port finder template
///
/// Template argument is whether the I/O port is required. It is a
@ -854,16 +892,6 @@ public:
/// remains valid until resolution time.
ioport_finder(device_t &base, char const *tag);
/// \brief Read I/O port if found or return default value
///
/// If the I/O port was found, this reads a value from the I/O port
/// and returns it. If the I/O port was not found, the default
/// value (supplied as a parameter) is returned.
/// \param [in] defval Value to return if I/O port was not found.
/// \return Value read from I/O port if found, or supplied default
/// value otherwise.
ioport_value read_safe(ioport_value defval) { return this->m_target ? this->m_target->read() : defval; }
private:
/// \brief Find I/O port
///

View File

@ -285,12 +285,12 @@ INPUT_PORTS_END
uint8_t maxaflex_state::pia_pa_r()
{
return atari_input_disabled() ? 0xff : m_joy01.read_safe(0);
return atari_input_disabled() ? 0xff : m_joy01->read();
}
uint8_t maxaflex_state::pia_pb_r()
{
return atari_input_disabled() ? 0xff : m_joy23.read_safe(0);
return atari_input_disabled() ? 0xff : m_joy23->read();
}

View File

@ -244,10 +244,10 @@ uint8_t meyc8088_state::input_r()
uint8_t ret = 0xff;
// multiplexed switch inputs
if (~m_common & 1) ret &= m_switches[0].read_safe(0); // bit switches
if (~m_common & 2) ret &= m_switches[1].read_safe(0); // control switches
if (~m_common & 4) ret &= m_switches[2].read_safe(0); // light switches
if (~m_common & 8) ret &= m_switches[3].read_safe(0); // light switches
if (~m_common & 1) ret &= m_switches[0]->read(); // bit switches
if (~m_common & 2) ret &= m_switches[1]->read(); // control switches
if (~m_common & 4) ret &= m_switches[2]->read(); // light switches
if (~m_common & 8) ret &= m_switches[3]->read(); // light switches
return ret;
}

View File

@ -1494,7 +1494,7 @@ void sun4c_state::sun4c(machine_config &config)
m_maincpu->set_mmu(m_mmu);
// SBus
SBUS(config, m_sbus, 20'000'000, "maincpu", "type1");
SBUS(config, m_sbus, 20'000'000, m_maincpu, m_type1space, 0);
m_sbus->irq<0>().set(FUNC(sun4c_state::sbus_irq_w<SPARC_IRQ1>));
m_sbus->irq<1>().set(FUNC(sun4c_state::sbus_irq_w<SPARC_IRQ2>));
m_sbus->irq<2>().set(FUNC(sun4c_state::sbus_irq_w<SPARC_IRQ3>));

View File

@ -192,7 +192,7 @@ u8 topspeed_state::input_bypass_r()
{
// Read port number
const u8 port = m_tc0040ioc->port_r();
const u16 steer = 0xff80 + m_steer.read_safe(0);
const u16 steer = 0xff80 + m_steer->read();
switch (port)
{
@ -209,14 +209,14 @@ u8 topspeed_state::input_bypass_r()
CUSTOM_INPUT_MEMBER(topspeed_state::gas_pedal_r)
{
static const u8 retval[8] = { 0,1,3,2,6,7,5,4 };
return retval[m_gas.read_safe(0) & 7];
static constexpr u8 retval[8] = { 0,1,3,2,6,7,5,4 };
return retval[m_gas->read() & 7];
}
CUSTOM_INPUT_MEMBER(topspeed_state::brake_pedal_r)
{
static const u8 retval[8] = { 0,1,3,2,6,7,5,4 };
return retval[m_brake.read_safe(0) & 7];
static constexpr u8 retval[8] = { 0,1,3,2,6,7,5,4 };
return retval[m_brake->read() & 7];
}
// TODO: proper motorcpu hook-up

View File

@ -16,7 +16,7 @@ DECLARE_DEVICE_TYPE(DOOYONG_RAM_TILEMAP, dooyong_ram_tilemap_device)
class dooyong_tilemap_device_base : public device_t
{
public:
template <typename T> void set_gfxdecode_tag(T &&cpu_tag) { m_gfxdecode.set_tag(std::forward<T>(cpu_tag)); }
template <typename T> void set_gfxdecode_tag(T &&gfxdecode_tag) { m_gfxdecode.set_tag(std::forward<T>(gfxdecode_tag)); }
void set_gfxnum(int gfxnum) { m_gfxnum = gfxnum; }
void draw(screen_device &screen, bitmap_ind16 &dest, rectangle const &cliprect, u32 flags, u8 priority, u8 priority_mask = 0xff);