Device Interfaces

From MAMEDEV Wiki

Device interfaces are separate classes that enable devices to participate in more areas of the overall system. Each interface is a sort of contract between the device that inherits from it and related other parts of the system. For example, a device inherits from the execute interface if it wishes to be scheduled and called regularly to execute. Simiarly, a device inherits from the NVRAM interface if it wishes to participate in saving/loading data to the .nv files.

To add an interface to a device, both the device class and device's configuration class must inherit from the interface's class and the interface's configuration class, respectively. Some interfaces are more about configuration than runtime, and others are more about runtime than configuration, but in all cases both interface classes are required.

Since a device is already required to inherit from device_t, this means that you must leverage C++'s multiple inheritance support in order to add an interface. For example, our configuration class might look like this:

class example1_device_config : public device_config,
                               public device_config_memory_interface,
                               public device_config_nvram_interface
{
};

and our device class like this:

class example1_device : public device_t,
                        public device_memory_interface,
                        public device_nvram_interface
{
};

Notice first that the device_config and device_t classes remain the first classes listed. This is important as they are the real base classes of our device. The interface classes should always be listed afterwards. Also notice that our configuration class and our device class inherit from matching configuration and interface classes.

Each interface has its own demands on the device. Some interfaces, like the sound interface, really don't require any significant changes. Others, like the NVRAM interface, are pure virtual classes, requiring implementation of one or more methods.

Currently the MAME core defines six standard interfaces:

  • The execute interface connects the device to the internal scheduler, and requires implementation of a device_run() method.
  • The memory interface connects the device to the memory system, allowing it to specify one or more address spaces.
  • The state interface connects the device to the debugger, enabling display and editing of state during execution from within the register view. It also provides simple indexed accessors for reading/writing state in a standard fashion.
  • The NVRAM interface connects the device to the NVRAM read/write process.
  • The disassembly interface connects the device to the debugger, enabling disassembly views from within the disassembly window.
  • The sound interface connects the device to the sound network, enabling routing of sound from the device to/from other devices.

Details on the interfaces and their requirements are given in the sections below.

Standard Interfaces

This section has descriptions of each of the standard device interfaces.

Execute

The execute interface enables a device to participate in the standard device scheduling. The scheduler makes a list of all devices with execute interfaces, and then iterates through them, assigning each a timeslice and requesting the device to execute. The execute interface also provides mechanisms for interrupt signalling and synchronization, allowing the device to participate properly in the execution of the aggregate system.

A device configuration class that inherits from device_config_execute_interface can optionally override several methods that describe how the device executes:

class example2_device_config : public device_config,
                               public device_config_execute_interface
{
    // normal device stuff here
protected:
    // device_config_execute_interface overrides
    virtual UINT32 execute_clocks_to_cycles(UINT32 clocks) const;
    virtual UINT32 execute_cycles_to_clocks(UINT32 cycles) const;
    virtual UINT32 execute_min_cycles() const;
    virtual UINT32 execute_max_cycles() const;
    virtual UINT32 execute_input_lines() const;
    virtual UINT32 execute_default_irq_vector() const;
};

The first two overrides provide methods that translate from clocks to cycles and vice-versa. A clock is defined as a single cycle of the input clock to the device. A cycle is defined as an internal unit used by the device when executing. By default, it is assumed that 1 clock == 1 cycle. However, if your device has an internal clock divider or multiplier, these methods should be overridden to do the math and return the proper numbers.

The middle two overrides should simply return the minimum and maximum number of cycles that any given execution step in your device may take. For example, if you are describing a CPU whose fastest instruction takes 2 cycles and whose slowest instruction takes 10 cycles, you would override these two methods to return the values 2 and 10, respectively. Note that these numbers are in cycles, not clocks. If not provided, these values both default to 1 cycle.

The execute_input_lines() override should simply return the number of synchronized input lines attached to the device. By default, this is 0.

Finally, the execute_default_irq_vector() override should return the default integer vector that is implicitly specified when signalling an input line without explicitly providing a vector.


Memory

State

NVRAM

Disassembly

Sound

Custom Interface