You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Both libONI and the underlying spec have some low-level quirks, sometimes motivated by the wide hardware range it's supposed to support, that are worthy of note for this project, specially if clroni is going to be included in the refactor, which can be a good idea.
Read operation
Since not all the device drivers support asynchronous transfers, calls to oni_read_frame() are blocking and assumed synchronous. While the oni driver translators might use asynchronous calls if available, this is not assured. So, It must be assumed that transactions are not being preformed in the background, but started and finished within an oni_read_frame() call.
This means that for optimum performance, there should be a dedicated thread calling the function, passing the frame handles to another thread through a queue or similar, and calling it again, in a continuous loop.
Calls to oni_read_frame() not only are blocking, but have no native timeout and expect the hardware to eventually return BLOCK_READ_SIZE bytes. The ONI hardware ensures this, barring catastrophic errors. This means that in such an error, the read thread might be blocked, along with any lock that it might have acquired. If an external timeout or force stop is implemented (e.g.: stopping bonsai flow and forcing a stop before it returns data, to recover from a hardware error) the read thread should be externally killed, all locks released and the context closed to force a hardware reset.
These both conditions, also mean that if acquisition were stopped before oni_read_frame() had enough data, it could be frozen (note: we have plans to solve this in a low-level driver independent manner, but is still a WIP). So, a normal sequence of events for acquisition would be:
Set the acquisition register to active
Start read thread
Normal operation
Stop signals thread to end
Read thread exits naturally from oni_read_frame() and, following the signal, ends the loop and finishes
Set the acquisition register to inactive and/or close the context
Frames
Frames are required to be a small as possible while containing as much information about a device output as possible. This usually means a single sample (e.g.: A sample of all 64chans on a RHD2164 device, a sample with all positional dat on a BNO...) but as long as there is a shared meaning, it can be part of one (e.g.: A line in a video feed). Samples from a device always have a fixed size, as specified by the device map. (Note: variable frame size is something we are considering for a future version, but is not currently on the roadmap).
Calls to oni_read_frame() returns a pointer to a frame structure that contains all the required fields to operate. When a frame lifetime has ended, it must be freed using oni_destroy_frame() An important note is that the data field of a frame structure is not a self-isolated copy of the original data received from the hardware, but a pointer to the point of the receive buffer where the frame data starts. This zero-copy approach was designed to increase performance and latency. The receive buffers are reference-counted and their lifetime managed by liboni, with oni_read_frame() creating new buffers as needed and oni_destroy_frame() freeing them when there are no live frames using them.
It would be interesting if a revamped Bonsai.ONIX / clroni could make use of this zero-copy to construct device frames to not only avoid copies but also the creation of redundant intermediate objects (more of this on another discussion).
Device map and frame size
The device map is sent from the hardware to the library automatically on context initialization (automatic reset), but can also be requested by manually issuing a reset command without the need to close and reopen the physical channel.
This is specially relevant for devices that have registers to set their frame size. Since frame sizes are constant, changes to such registers are only made when a reset command is issued (manually or by context reopening) and a new device map is received. This also means that changes to these registers during acquisition will not take any effect. Resetting stops acquisition so any reset request must be done outside of it.
Since device map transmission is a relatively slow process, the naïve approach of just issuing a reset each time such a register is changed is discouraged. The best solution is to only issue one, after setting the registers to configure all the devices and before starting acquisition, ideally only if one or more of such registers have changed.
Device registers
Access to device registers is a slow process that, depending on the oni physical layer, can be detrimental to high-speed data bandwidth. As such, is recommended to minimize register access while acquisition is running, except when needed. Of course, if a device has runtime registers and those are changed, a write command needs to be issued, but unnecessary accesses, specially redundant reads, need to be avoided while acquiring data.
Most register devices are permanent and retain their value after a reset or a context close and reopen, only returning to their default value on power cycle. This allows procedures like calling reset, as described in the previous topic, without disrupting their settings. However, albeit rare, there might be registers that return to a default value on a reset command. These are usually meant for control during acquisition (e.g.: stimulus enable controls). The reset behavior of registers is clearly specified in the device datasheets so if needed, an attribute could be used in their respective nodes to be aware of this behavior.
Finally, there are some device registers that are not meant to be directly exposed to an end-user, but transformed. The most common example is the frequency of event generators (e.g.: Heartbeat or Load Tester). The user will want to specify this frequency in Hz. However, the device exposes a Read-Write register for a clock divider and a Read-Only register with the clock frequency in Hz, so setting this parameter will require a register read, transformation of the value and a write.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
Both libONI and the underlying spec have some low-level quirks, sometimes motivated by the wide hardware range it's supposed to support, that are worthy of note for this project, specially if clroni is going to be included in the refactor, which can be a good idea.
Read operation
Since not all the device drivers support asynchronous transfers, calls to
oni_read_frame()
are blocking and assumed synchronous. While the oni driver translators might use asynchronous calls if available, this is not assured. So, It must be assumed that transactions are not being preformed in the background, but started and finished within anoni_read_frame()
call.This means that for optimum performance, there should be a dedicated thread calling the function, passing the frame handles to another thread through a queue or similar, and calling it again, in a continuous loop.
Calls to
oni_read_frame()
not only are blocking, but have no native timeout and expect the hardware to eventually return BLOCK_READ_SIZE bytes. The ONI hardware ensures this, barring catastrophic errors. This means that in such an error, the read thread might be blocked, along with any lock that it might have acquired. If an external timeout or force stop is implemented (e.g.: stopping bonsai flow and forcing a stop before it returns data, to recover from a hardware error) the read thread should be externally killed, all locks released and the context closed to force a hardware reset.These both conditions, also mean that if acquisition were stopped before
oni_read_frame()
had enough data, it could be frozen (note: we have plans to solve this in a low-level driver independent manner, but is still a WIP). So, a normal sequence of events for acquisition would be:oni_read_frame()
and, following the signal, ends the loop and finishesFrames
Frames are required to be a small as possible while containing as much information about a device output as possible. This usually means a single sample (e.g.: A sample of all 64chans on a RHD2164 device, a sample with all positional dat on a BNO...) but as long as there is a shared meaning, it can be part of one (e.g.: A line in a video feed). Samples from a device always have a fixed size, as specified by the device map. (Note: variable frame size is something we are considering for a future version, but is not currently on the roadmap).
Calls to
oni_read_frame()
returns a pointer to a frame structure that contains all the required fields to operate. When a frame lifetime has ended, it must be freed usingoni_destroy_frame()
An important note is that the data field of a frame structure is not a self-isolated copy of the original data received from the hardware, but a pointer to the point of the receive buffer where the frame data starts. This zero-copy approach was designed to increase performance and latency. The receive buffers are reference-counted and their lifetime managed by liboni, withoni_read_frame()
creating new buffers as needed andoni_destroy_frame()
freeing them when there are no live frames using them.It would be interesting if a revamped Bonsai.ONIX / clroni could make use of this zero-copy to construct device frames to not only avoid copies but also the creation of redundant intermediate objects (more of this on another discussion).
Device map and frame size
The device map is sent from the hardware to the library automatically on context initialization (automatic reset), but can also be requested by manually issuing a reset command without the need to close and reopen the physical channel.
This is specially relevant for devices that have registers to set their frame size. Since frame sizes are constant, changes to such registers are only made when a reset command is issued (manually or by context reopening) and a new device map is received. This also means that changes to these registers during acquisition will not take any effect. Resetting stops acquisition so any reset request must be done outside of it.
Since device map transmission is a relatively slow process, the naïve approach of just issuing a reset each time such a register is changed is discouraged. The best solution is to only issue one, after setting the registers to configure all the devices and before starting acquisition, ideally only if one or more of such registers have changed.
Device registers
Access to device registers is a slow process that, depending on the oni physical layer, can be detrimental to high-speed data bandwidth. As such, is recommended to minimize register access while acquisition is running, except when needed. Of course, if a device has runtime registers and those are changed, a write command needs to be issued, but unnecessary accesses, specially redundant reads, need to be avoided while acquiring data.
Most register devices are permanent and retain their value after a reset or a context close and reopen, only returning to their default value on power cycle. This allows procedures like calling reset, as described in the previous topic, without disrupting their settings. However, albeit rare, there might be registers that return to a default value on a reset command. These are usually meant for control during acquisition (e.g.: stimulus enable controls). The reset behavior of registers is clearly specified in the device datasheets so if needed, an attribute could be used in their respective nodes to be aware of this behavior.
Finally, there are some device registers that are not meant to be directly exposed to an end-user, but transformed. The most common example is the frequency of event generators (e.g.: Heartbeat or Load Tester). The user will want to specify this frequency in Hz. However, the device exposes a Read-Write register for a clock divider and a Read-Only register with the clock frequency in Hz, so setting this parameter will require a register read, transformation of the value and a write.
Beta Was this translation helpful? Give feedback.
All reactions