![]() |
ChimeraTK-DeviceAccess 03.29.00
|
This page describes the JMAP file format, which is a JSON based file format used to describe the register map of numeric-addressed devices.
It has been introduced to replace the classic plain-text map file format and to provide additional features which would not fit into the old format. The numeric-addressed backends (e.g. PCIe, XDMA, UIO, and the Dummy backend) can be told to use a JMAP file by passing the map parameter in the ChimeraTK device descriptor:
(pci:pciedevs5?map=mydevice.jmap) (dummy?map=mydevice.jmap)
Commonly the register maps are generated by firmware tools like the FWK firmware framework, but they can just as well be written by hand in case no such generation tool exists.
A JMAP file is a single JSON object with the following top-level keys:
mapFormatVersion - a version string of the file format (currently "0.0.1"). metadata - an object holding arbitrary key-value pairs describing the firmware (see Metadata). interruptHandler - a description of the interrupt controllers (see Interrupts). addressSpace - the dictionary of registers and modules making up the register map (each key is a register/module name).The heart of the file is the addressSpace object. Each key is the name of a register, or of a module which groups other registers and modules below it; the object stored under a key describes that register/module. Registers are addressed by a hierarchical path, which is built from the addressSpace / children dictionary keys (the names of the modules and registers), separated by '/' or '.', e.g. APP/STATUS or APP.STATUS ('/' and '.' have synonymous meaning and can be exchanged arbitrarily). This path is used to access the register through register accessors, as described in Multi Value Registers (1D Register Accessors) and 2D Register Accessors.
JSON does not support comments natively. By convention, entries whose key starts with an underscore are treated as comments and ignored by the parser:
{
"_": "This is a comment and is ignored",
"_comment": "This is another comment and is ignored as well",
"mapFormatVersion": "0.0.1",
"metadata": {},
"interruptHandler": {},
"addressSpace": {}
}
Any unknown key not starting with an underscore is reserved for future use in later map format versions and hence should not be used as comments.
A register is an entry carrying an address (or inheriting one from a parent module, see Modules and hierarchical names). This section describes the individual members of a register entry.
The minimal register entry is stored under its name and needs only an address:
"addressSpace": {
"SomeTopLevelRegister": {
"address": { "type": "IO", "channel": 0, "offset": 32 }
}
}
The address object has the following members:
type - either "IO" (the default, an address on the I/O bus) or "DMA". A DMA address is typically used for bulk data acquisition registers. channel - the bus channel (bar) number, defaults to 0. For DMA addresses the channel is mapped to a device bar by adding an offset of 13, mirroring the convention of the old map file format in which DMA bars started at an offset of 13 (so a DMA channel of 0 corresponds to device bar 13). offset - the byte offset of the register inside the channel, given either as a plain decimal number or as a hexadecimal string.An address may also be omitted and inherited from a parent module; this is described together with modules in Modules and hierarchical names.
The access member defines how a register may be accessed:
"RW" - read/write access (the default). "RO" - read-only access, e.g. for status registers. "WO" - write-only access, values cannot be read back.Example:
"addressSpace": {
"STATUS": {
"access": "RO",
"address": { "channel": 2, "offset": "0x8000" }
}
}
By default a register holds a single element of 4 bytes. A register holding more than one element is declared by giving the number of elements and the size of each element:
numberOfElements - the number of elements, defaults to 1. bytesPerElement - the size of a single element in bytes, defaults to 4.Example:
"addressSpace": {
"SomeTable": {
"access": "WO",
"numberOfElements": 16384,
"bytesPerElement": 2,
"address": { "channel": 0, "offset": 2048 }
}
}
The representation object describes how the raw data is interpreted by the application layer. The following members and values are supported:
type - one of "fixedPoint" (the default), "IEEE754" (single or double precision floating point, see below), "string" (an ASCII string) or "void" (used for pure interrupt sources, see Interrupts). width - the width of the value in bits. For fixedPoint it may be smaller than the element size, in which case the remaining bits are ignored. Default: 32 fractionalBits - the number of fractional bits of a fixedPoint value; may be negative to describe integer values with a scaling factor. Must be 0 unless type is fixedPoint. Default: 0 isSigned - whether the value is interpreted as signed. Must be true for IEEE754 types. Default: false bitShift - the bit offset of the value within the element. Default: 0 If the entire representation is omitted, it defaults to a 32-bit unsigned integer.
A single-precision floating point register:
"addressSpace": {
"ExampleRegister": {
"address": { "channel": 0, "offset": 32 },
"representation": {
"type": "fixedPoint",
"width": 26,
"fractionalBits": 12,
"isSigned": true
}
}
}
description - a free-form human-readable text describing the purpose of the register. engineeringUnit - the physical unit of the register value, e.g. "mV".Both are purely informational and may be omitted. Example:
"addressSpace": {
"SomeTopLevelRegister": {
"engineeringUnit": "mV",
"description": "This is an example register",
"address": { "channel": 0, "offset": 32 }
}
}
A register/module in the addressSpace without an address is a module: it groups nested registers and modules below it by providing a children dictionary keyed by the children's names, and contributes its own name (its addressSpace / children key) to the hierarchical path of everything beneath it. A module is not a register and carries no data of its own. Example (from simpleJsonFile.jmap):
"addressSpace": {
"APP": {
"children": {
"STATUS": {
"access": "RO",
"address": { "type": "IO", "channel": 2, "offset": "0x8000" }
},
"CONTROL": {
"access": "RW",
"address": { "type": "IO", "channel": 2, "offset": "0x8004" }
}
}
}
}
This yields the registers APP.STATUS and APP.CONTROL.
Note: a module simply becomes a register by adding an address. This can be used to describe a hierarchical address space by describing the raw address space e.g. for a firmware module at the higher level of the hierarchy, while adding the detailed registers in the firmware module's address space as children. Keep in mind that there is no technical necessity that all children have to have addresses inside the address space of their parents (although it is certainly a good convention). Example:
"addressSpace": {
"APP": {
"address": { "type": "IO", "channel": 2, "offset": "0x8000" },
"numberOfElements": 2,
"children": {
"STATUS": {
"access": "RO",
"address": { "type": "IO", "channel": 2, "offset": "0x8000" },
"representation": { "width": 3 }
},
"CONTROL": {
"access": "RW",
"address": { "type": "IO", "channel": 2, "offset": "0x8004" },
"representation": { "width": 5 }
}
}
}
}
This yields the same registers APP/STATUS and APP.CONTROL as before, but in addition a register APP for the raw area containing both registers.
A child entry does not have to carry an address of its own: it inherits the address of the nearest ancestor that does. The inheritance works per field of the address entry, so e.g. only the type and channel may be inherited while the offset is overridden. Example (identical outcome to the last example above):
"addressSpace": {
"APP": {
"address": { "type": "IO", "channel": 2, "offset": "0x8000" },
"numberOfElements": 2,
"children": {
"STATUS": {
"access": "RO",
"representation": { "width": 3 }
},
"CONTROL": {
"access": "RW",
"address": { "offset": "0x8004" },
"representation": { "width": 5 }
}
}
}
}
The interruptHandler top-level object describes interrupt controllers which are used to multiplex multiple interrupt sources in firmware into the same hardware interrupt request. Each key is an interrupt id and each entry carries an INTC controller with its configuration and optionally nested subhandler entries:
"interruptHandler": {
"3": {
"INTC": { "path": "MY_INTC", "options": ["MER"], "version": 1 },
"subhandler": {
"0": {
"INTC": { "path": "MY_INTC.SUB0", "options": [] }
}
}
}
}
Registers can be triggered by an interrupt. The "triggeredByInterrupt" field contains the full interrupt id, which is an array of all ids and sub-ids as described in the interruptHandler object. Such registers are always read-only, hence the "triggeredByInterrupt" field is mutually exclusive with the "access" field.
Plain interrupts have a representation of type "void" and must not have an address. Example:
"addressSpace": {
"PRIMARY_INTERRUPT_3": {
"triggeredByInterrupt": [3],
"representation": { "type": "void" }
},
"SUB_INTERRUPT_3_0_2": {
"triggeredByInterrupt": [3,0,2],
"representation": { "type": "void" }
}
}
PRIMARY_INTERRUPT_3 describes the primary interrupt (IRQ) 3, while SUB_INTERRUPT_3_0_2 will be the interrupt number 2 of the subhandler 0 inside the handler for the primary interrupt 3 (i.e. interrupt 3:0:2).
When interrupts shall be used to trigger data read-out, they can be added to normal registers:
"addressSpace": {
"TRIGGERED_DATA": {
"address": { "offset": "0x8100" },
"numberOfElements": 2048,
"triggeredByInterrupt": [3,0,2],
"representation": { "width": 24, "fractionalBits": 10, "isSigned": true }
}
}
This will create a push-type register (supporting ChimeraTK::AccessMode::wait_for_new_data) that will receive new values when the interrupt 3:0:2 arrives by polling the data from the specified address. See Design: AsyncNDRegisterAccessor for more details.
A register holding several channels of time-multiplexed samples is declared by giving a channelTabs entry:
numberOfElements - the number of samples (time slots) per channel. pitch - the number of bytes between two samples of the same channel. channels - the channels of this tab, given as a dictionary keyed by channel name. Each channel value has a byte offset relative to the register base address, a bytesPerElement and an optional representation. (Channel order in memory follows the offset, independent of the order of the dictionary keys.)Channels can have arbitrary names; single-word channels are simply indexed "0", "1", ... Example:
"addressSpace": {
"DMA": {
"address": { "type": "DMA", "offset": "0x0" },
"channelTabs": [
{
"numberOfElements": 16384,
"pitch": 32,
"channels": {
"0": {
"offset": 0,
"bytesPerElement": 2,
"representation": { "type": "fixedPoint", "width": 16 }
},
"1": {
"offset": 2,
"bytesPerElement": 2,
"representation": { "type": "fixedPoint", "width": 12, "fractionalBits": 12 }
}
}
}
]
}
}
Here DMA is a 2D register with two channels, each holding 16384 samples, with a pitch of 32 bytes between the samples of a channel (so there is space for more channels in this example).
For every channel, a read-only one-dimensional register slice is created automatically. The slice is named after the channel, e.g. DMA/0, DMA/1 in the example above. Each slice exposes the samples of that single channel as a one-dimensional array, reading them out of the 2D register with the correct stride. The channel's byte offset is folded into the slice address, so it can be addressed independently of the parent 2D register. Writing is deliberately not supported for these slices, since writing a single channel of a 2D register would require a read-modify-write cycle across the channels. If multiple slice accessors for the same 2D register shall be read simultaneously (without ChimeraTK::AccessMode::wait_for_new_data), it is highly recommended to add them all to the same ChimeraTK::TransferGroup to avoid transferring the full 2D data multiple times unnecessarily from the device.
The example above uses a single channel tab, which is the common case. For multi-tab DAQ setups an additional tabSelectRegister can be used to switch between the tabs, but the support for multiple tabs is not yet implemented.
A register that is fed by double-buffered DMA hardware is marked with a doubleBuffering object. It contains:
secondaryBufferAddress - the address of the second (inactive) buffer, with the same members as a regular address. enableRegister - path of the register used to enable double buffering. readBufferRegister - path of the register reporting which buffer is currently active. index - the index of the buffer pair within the control registers.In addition to the main register, two read-only registers named NAME/BUF0 and NAME/BUF1 are created for the two buffers. Example:
"addressSpace": {
"CTRL": {
"triggeredByInterrupt": [3, 0, 1],
"address": { "type": "DMA", "offset": "0x40000" },
"doubleBuffering": {
"secondaryBufferAddress": { "type": "DMA", "offset": "0x40200" },
"enableRegister": "DAQ.DOUBLE_BUF.ENA",
"readBufferRegister": "DAQ.DOUBLE_BUF.INACTIVE_BUF_ID",
"index": 0
},
"channelTabs": [
{
"numberOfElements": 16384,
"pitch": 64,
"channels": {
"errorI": {
"bytesPerElement": 2,
"offset": 0,
"representation": {
"type": "fixedPoint",
"width": 16,
"fractionalBits": -2,
"isSigned": true
}
}
}
}
]
}
}
A register can carry a children dictionary of entries which are bit fields of the register word. Each child key is the bit-field's name and its value a representation with bitShift and width; the children share the starting address of their parent register (see Address inheritance). The full word register itself is also accessible. Example:
"addressSpace": {
"STATUS": {
"access": "RO",
"address": { "type": "IO", "channel": 2, "offset": "0x8000" },
"bytesPerElement": 4,
"children": {
"ProbeLimiter": {
"representation": { "width": 1 }
},
"ExternalInterlock": {
"representation": { "bitShift": 1, "width": 1 }
},
"ErrorCounter": {
"representation": {
"bitShift": 2,
"type": "fixedPoint",
"width": 3,
"fractionalBits": 0,
"isSigned": false
}
}
}
}
}
Registers which share the starting address with a bit range and have a width smaller than their element size are automatically recognised as bit ranges as well.
The metadata object can carry any number of key-value pairs describing the firmware. Keys are required to be non-empty and must not start with an underscore. All entries are collected into the metadata catalogue and can be queried by application code. Example:
"metadata": {
"mapfileRevision": "1.8.3-0-gdeadbeef",
"someRandomEntry": "some random value"
}