MAME Device Basics

From MAMEDEV Wiki

Overview

In MAME, a device is a mechanism for encapsulating behavior. While it is common to associate a device (in the MAME sense) with a physical device (in the real world), there does not necessarily need to be a 1:1 correspondance between the two.

Devices are important because they provide clean hooks into the MAME system. They are notified when key things in the system change, they encode their own configuration information, keep their own state, and can be instantiated multiple times within a given machine. They are easily located via a simple string tag, and are first-class citizens in memory maps, so they are easily read from and written to.

As of MAME 0.139, devices are implemented using a collection of C++ classes. In order to provide the flexibility necessary to describe the sorts of devices in MAME, the device model relies heavily on the multiple inheritance feature of C++ to extend devices with one or more device interfaces.

Basic Concepts

Every device in the project is built up out of two classes: a device-specific configuration class (derived from the device_config class) and a device-specific runtime class (derived from the device_t class).

The configuration class is responsible for encapsulating the device's configuration. The base device_config class automatically supports several core configuration properties, such as a short string tag to identify the device instance, a clock value which represents the input clock to the device, and an owner who serves as the device's parent. All device-specific configuration classes must be derived from the device_config class at their root.

Of course, most devices require more configuration than this, and so there are mechanisms for the device-specific configuration class to accept further configuration information, both inline in the MACHINE_CONFIG description, as well as externally in a static structure. This additional configuration data is stored in the device-specific class. More details on how this works come later in this article.

In addition to holding the configuration of a device, the device-specific configuration class also serves as a "factory" class that provides a mechanism for the MAME core to instantiate both new configuration objects, via a static method, and new runtime objects, via a required virtual method. (It is worth noting that the pointer to the static method that constructs configuration objects also serves as the device "type", which is a unique single entry point into the device.)

The runtime class, as its name implies, holds the runtime state of a device. The base device_t class provides a number of basic device concepts, including device initialization, reset, hooks into the save state system, clock scaling. It also holds a reference back to the corresponding device_config that begat the device.

The device-specific runtime class, which is required to derive from device_t, then contains all the runtime state of the device, along with methods to operate upon the live device. It can also override several internal methods of its parent class to gain access to hooks that are called during specific events in the machine's lifecycle.

Lifecycle of a Device

Configuration

Machine configurations in MAME are represented by a tokenizing mechanism wrapped by macros. A typical machine driver looks something like this (having removed some of the irrelevant details):

static MACHINE_DRIVER_START( pacman )
    MDRV_CPU_ADD("maincpu", Z80, MASTER_CLOCK/6)
    MDRV_CPU_PROGRAM_MAP(pacman_map)
    ...
    MDRV_SCREEN_ADD("screen", RASTER)
    MDRV_SCREEN_FORMAT(BITMAP_FORMAT_INDEXED16)
    ...
    MDRV_SOUND_ADD("namco", NAMCO, MASTER_CLOCK/6/32)
    MDRV_SOUND_CONFIG(namco_config)
    MDRV_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.0)
MACHINE_DRIVER_END

When the compiler processes this, the MDRV_* macros all map down to a set of 32-bit or 64-bit integral tokens which are stored as a stream for later processing.

It may not be immediately obvious, but the machine configuration above defines three separate devices: a CPU device called "maincpu", a video screen device called "screen", and a Namco sound device called "namco". Each device generally defines its own MDRV_*_ADD() macro which permits some flexibility in how each device is added. The MDRV_* macros that follow each device provide configuration information. More on configuration in a later chapter.

When a machine configuration is instantiated, it first takes the token stream and executes it, creating a device configuration whenever it sees an MCONFIG_TOKEN_DEVICE_ADD token (which is output by the MDRV_*_ADD() macro mentioned above), and populating the device configuration with data from subsequent macros.

One of the parameters to MCONFIG_TOKEN_DEVICE_ADD is a device type. In MAME a device type is a static function pointer which serves as the factory function for allocating a device configuration. So when we need to add a device, we simply call the factory function and ask it to allocate for us a new device configuration of the appropriate type.

Once the configuration is allocated, we continue to process tokens. Tokens within a certain well-defined range are known to be device configuration tokens, and these are handed off to the allocated device configuration for processing. Specific devices can also support their own custom-defined tokens if they need special behaviors (the MDRV_SOUND_ROUTE above does this) by overriding the device_process_token() method in the device-specific configuration class.

Upon encountering the end of the token stream, all the devices are notified that the configuration parsing is complete. This allows them to consolidate any configuration information or do any other work that needs to be done. Device-specific configuration classes can override the device_config_complete() method to hook into this event.

There are several situations in which the machine configuration and all the device configurations are created: to perform validity checks on all the drivers; to output information needed by the -listxml and other front-end functions; to check for vector screens when parsing .ini files; and finally, in preparation for starting an actual game. In all cases but the last one, the machine and device configurations are created and discarded without ever creating any runtime devices, so the device lifecycle can very well begin and end with the device configuration.

In the case where validity checks are performed, the device-specific configuration class has the option of performing its own validation by overriding the device_validity_check() method and outputting errors if any are found. For this reason, validation should happen here rather than in the device_config_complete(), so that errors can be reported in a consistent manner.

Runtime

When the time comes to create a running machine object and start up the devices, MAME will take the device list contained in the machine configuration and rip through it to allocate the runtime devices. The mechanism for allocating a runtime device is to call the device-specific configuration class's device_alloc() method, whose job is simply to auto_alloc an instance of the device-specific runtime class. This method is a required override.

(more later)

Configuring Devices

  • Static configs
  • Inline configs

Interfaces in Detail

Best Practices