System Definitions
A system’s devices, busses, clock sources, and connections between all of these are defined in system definitions, human readable TOML structures.
Most items defined in the definition have an associated name, specified in the name
key. This name is unique to that instance in that system, and is used to uniquely identify that particular bus, clock source, or device for purposes of connections. (This implies that each of these three types has a separate namespace.) This file can be roughly divided into four separate sections, each defined as a table in the file:
System Information
This table provides general metadata about the system under the system
key. Data under this key is primarily used for presentation to the user. The following metadata values are defined:
name
: Name of the system, user visible.manufacturer
: Manufacturer of the system, user visible.
Busses
Systems will contain one or more busses that connect devices together. These are abstract “pipes” for moving an (address, data) pair, each of which has a fixed width.
Mandatory for each bus is specifying the width of the data bus via the dataWidth
property, and the width of the address bus via the addrWidth
property.
[[busses]]
name = "68k"
addrWidth = 24
dataWidth = 16
pullup = 0xFFFF
[[busses.map]]
range = [ 0x000000, 0x3FFFFF ]
target = "cartridge"
[[busses.map]]
range = [ 0xE00000, 0xFFFFFF ]
target = "mainram"
Address Mapping
Address decoding of devices on the bus is handled internally by the emulation library. To enable this, the system definition defines a mapping of address ranges to which devices shall receive the bus transaction in this case. This is achieved through an array under the map
key.
Each entry in the address map consists of two keys. First, target
which indicates the name of the device that should be mapped at this address range. Next is range
, which is an array of two unsigned integers corresponding to the [start, end] address range to connect this device to.
Devices are expected to implement any address wrap-around (as a result of incomplete decoding; e.g. 64K of RAM are mirrored through 2M of address space) instead of relying on the bus to handle it.
Pullup/Pulldown Support
It’s common for peripherals to only drive part of a bus, leading to undefined contents on undriven bus lines. To guard against this, many systems have some sort of pullup or pulldown resistors on bus lines so they consistently read as a particular value if they’re not being driven.
Specify either the pulldown
or pullup
key to indicate which bits “float” to 1 or 0, respectively, during a bus transaction if they are not being actively driven. It’s not allowed to specify a bit to be both pulled up and down. (This makes no sense in the real world, either: the signal would likely end up being in the “indeterminate” zone between a 0 and 1.)
If these keys are omitted, or no pulls are specified for a particular bit, they will retain their most recently written value if not driven in a subsequent transaction.
Clock Sources
Every system should specify at least one clock source, from which models an oscillator in a real system. These clock sources are defined in an array stored under the clocks
key in the definition. Common to all clock source types is the name
key, which specifies their connection name.
A clock source may either be primary — with a directly specified frequency — or a derived clock, which applies a divisor to another clock to produce its output frequency. The system definition parser determines the clock type based on the presence of the parent
key; if it’s missing, the clock is assumed to be primary, otherwise derived.
Primary Clocks
For a primary clock, the only required key is freqs
, which should be an array that defines the frequency “variants” for this clock source, of which there must always be at least one. Variants can be specified either as a plain frequency (double or integer is ok, in Hz) or as a table with the name
and freq
key, to allow a more user-friendly description of a frequency variant.
[[clocks]]
name = "MCLK"
[[clocks.freqs]]
name = "NTSC"
freq = 53693175
[[clocks.freqs]]
name = "PAL"
freq = 53203424
Derived Clocks
Most systems have more than one clock source. These clock sources are usually however all derived from one or more primary clock sources, usually through simple integer dividers. Derived clocks have a parent
key, which indicates which clock source they use as their base; and a divisor
key, which indicates a floating point number to apply as the frequency divisor. (The frequency of the parent is multiplied by 1/divisor)
[[clocks]]
name = "VCLK"
parent = "MCLK"
divisor = 7
Devices
Lastly, the system definition defines the actual devices operating in the system, which may be unique to the system and defined in external plugins, as an array under the devices
key. Each entry in this array should be a table, with at least the name
key.
The type of device to instantiate is defined by the contents of the type
key. This is a comma separated list of short device names (thus these must be unique; there’s no requirement for the name format, but reverse DNS style works well) in descending preference order. This allows for more generic device types to be instantiated if the precise version of the device is not available.
[[devices]]
type = "psram,ram"
name = "mainram"
size = 65536
contents = "random"
access = 120
[[devices.connections]]
name = "busslot"
target = "68k"
type = "bus"
[[devices.connections]]
name = "clockslot"
target = "MCLK"
type = "clock"
Connections
Devices may be connected to busses, clock sources, or other devices. These connections are defined via the connections
array key, which in turn contains tables with the following keys:
type
: Type of object being connected; may be one ofbus
,clock
, ordevice
.name
: Name of the connection slot on the device being defined.target
: Name of the device that will be connected to this slot.
Note that bus connections are implicitly made when a device is specified in a memory map, and are in most cases enough. Busses are usually only explicitly specified for devices that can perform bus mastering, such as processors and DMA controllers.
Additional Properties
Any keys not explicitly mentioned above (there is a list of ignored keys when constructing the arguments) are passed to the device constructor, and their interpretation is specific to the device class.
Emulation Strategy
The way the device is emulated (e.g. its time slice, maximum runahead, threading, etc.) is determined by the keys in the table in the device record under the emulation
key. For most devices, it is mandatory; only devices that are passive (that do not have an emulation step) can in all cases omit this. See this page for an overview of the available emulation strategies.
Eventually, the emulator should be smart enough to figure out the relationships and most optimal threading modes, but for now, that's what this key is for.
Time Slice
All devices that use the time slice emulation strategy will receive their own per instance execution contexts. What this amounts to is that there's not a whole lot of control over this process.
length
: Duration of the time slice, in nanoseconds. Longer time slices can be more efficient and reduce overhead; however, this may result in an increased amount of state rollbacks if the device is frequently accessed.- Currently, this value can only be specified directly. In the future, it should be possibly to specify it in terms of a clock source.
Step
For devices that use step based emulation (most all processors) most options pertain on what other devices (if any) the step shares an execution with. Stepped devices are executed cooperatively, in coroutine style.
group
: String name of an execution group that this device is stepped in. You do not need to define an execution group before it is used.runahead
: Maximum number of nanoseconds the device may be stepped ahead of other devices in the system.