ChimeraTK-DeviceAccess 03.31.00
Loading...
Searching...
No Matches
NumericAddressedBackendMuxedRegisterAccessor.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
5
6namespace ChimeraTK {
7
8 /********************************************************************************************************************/
9
10 template<class UserType>
12 const RegisterPath& registerPathName, size_t numberOfElements, size_t elementsOffset,
13 const boost::shared_ptr<DeviceBackend>& _backend)
14 : NDRegisterAccessor<UserType>(registerPathName, {}),
15 _ioDevice(boost::dynamic_pointer_cast<NumericAddressedBackend>(_backend)) {
16 // Obtain information about the area
17 _registerInfo = _ioDevice->_registerMap.getBackendRegister(registerPathName);
18 assert(!_registerInfo.channels.empty());
19
20 // check information
21 if(_registerInfo.elementPitchBits % 8 != 0) {
22 throw ChimeraTK::logic_error("NumericAddressedBackendMuxedRegisterAccessor: blocks must be byte aligned.");
23 }
24
25 // A 2D register whose channel is a direct bit range (e.g. a byte-aligned bitShift that would otherwise pass the
26 // byte-alignment checks and silently yield wrong values) cannot be read through the full-2D-register accessor.
27 // Bit ranges are exposed through the named-channel slices only.
28 if(_registerInfo.isBitRange) {
29 throw ChimeraTK::logic_error("NumericAddressedBackendMuxedRegisterAccessor: register '" + registerPathName +
30 "' contains bit-range channels. Access them through the named-channel slices.");
31 }
32
33 // Helper struct containing everything in ChannelInfo except the bitOffset. This helps to identify which channels
34 // can share the RawConverter.
35 struct ConverterInfo {
36 NumericAddressedRegisterInfo::Type dataType;
37 uint32_t width;
38 int32_t nFractionalBits;
39 bool signedFlag;
40 DataType rawType;
41
42 // Comparison is required to use objects as std::map key
43 auto operator<=>(const ConverterInfo& rhs) const = default;
44
45 // construct from ChannelInfo
46 explicit ConverterInfo(const NumericAddressedRegisterInfo::ChannelInfo& o)
47 : dataType(o.dataType), width(o.width), nFractionalBits(o.nFractionalBits), signedFlag(o.signedFlag),
48 rawType(o.rawType) {}
49 };
50
51 // Identify groups of channels with identical raw conversion parameters. All channels in one group can hence share
52 // the same RawConverter. This allows for some performance optimisation in the de-multiplexing and conversion step.
53 std::map<ConverterInfo, size_t> groupInfoMap;
54 for(size_t channelIndex = 0; channelIndex < _registerInfo.getNumberOfChannels(); ++channelIndex) {
55 const auto& info = _registerInfo.channels[channelIndex];
56
57 if(info.bitOffset % 8 != 0) {
58 throw ChimeraTK::logic_error("NumericAddressedBackendMuxedRegisterAccessor: elements must be byte aligned.");
59 }
60
61 // Check if a matching group already exists in the groupInfoMap
62 const auto converterInfo = ConverterInfo(info);
63 auto it = groupInfoMap.find(converterInfo);
64 if(it == groupInfoMap.end()) {
65 // No matching group exists yet, so create new ChannelGroup containing the converter
66 auto newGroupId = groupInfoMap.size();
67 groupInfoMap[converterInfo] = newGroupId;
68
69 ChannelGroup newGroup;
70 newGroup.channels.emplace_back(
71 channelIndex, 0, typename std::vector<UserType>::iterator()); // offsetToNext is set later
72 newGroup.converterLoopHelper = RawConverter::ConverterLoopHelper::makeConverterLoopHelper<UserType>(
73 _registerInfo, channelIndex, newGroupId, *this);
74 newGroup.startOffset = info.bitOffset / 8;
75
76 // Store the group and update the group map
77 _channelGroups.emplace_back(std::move(newGroup));
78 }
79 else {
80 // Matching group found: Add channel to group and update the group map
81 _channelGroups[it->second].channels.emplace_back(
82 channelIndex, 0, typename std::vector<UserType>::iterator()); // offsetToNext is set later
83 }
84 }
85 assert(_channelGroups.size() == groupInfoMap.size());
86
87 // Fill the "offsetToNext" field of the ChannelGroup::Channel struct. This cannot be done in the above loop, since
88 // we need to know already which is the next channel in the group.
89 for(auto& group : _channelGroups) {
90 assert(group.channels.size() >= 1);
91 // fill offsets for all channels in group except the last one
92 for(size_t i = 0; i < group.channels.size() - 1; ++i) {
93 auto bitOffset = _registerInfo.channels[group.channels[i + 1].index].bitOffset -
94 _registerInfo.channels[group.channels[i].index].bitOffset;
95 assert(bitOffset % 8 == 0);
96 group.channels[i].offsetToNext = bitOffset / 8;
97 }
98 // offset for the last channel in the group needs to point to the first channel in group (next sample)
99 auto lastBitOffset = _registerInfo.elementPitchBits -
100 _registerInfo.channels[group.channels.back().index].bitOffset +
101 _registerInfo.channels[group.channels.front().index].bitOffset;
102 assert(lastBitOffset % 8 == 0);
103 group.channels.back().offsetToNext = lastBitOffset / 8;
104 }
105
106 // compute effective numberOfElements
107 if(numberOfElements == 0) {
108 numberOfElements = _registerInfo.nElements;
109 }
110
111 // check number of words
112 if(numberOfElements + elementsOffset > _registerInfo.nElements) {
113 throw ChimeraTK::logic_error("Requested number of elements (" + std::to_string(numberOfElements) + " + " +
114 std::to_string(elementsOffset) + ") exceeds the size (" + std::to_string(_registerInfo.nElements) +
115 ") of the register '" + registerPathName + "'!");
116
117 throw ChimeraTK::logic_error("Requested number of elements exceeds the size of the register! Requested end: " +
118 std::to_string(numberOfElements + elementsOffset) +
119 ", register length: " + std::to_string(_registerInfo.nElements));
120 }
121
122 // update register info
123 _registerInfo.nElements = numberOfElements;
124 assert(_registerInfo.elementPitchBits % 8 == 0);
125 _registerInfo.address += elementsOffset * _registerInfo.elementPitchBits / 8;
126
127 // allocate the buffer for the converted data
128 NDRegisterAccessor<UserType>::buffer_2D.resize(_registerInfo.getNumberOfChannels());
129 for(auto& buf : NDRegisterAccessor<UserType>::buffer_2D) {
130 buf.resize(_registerInfo.nElements);
131 }
132
133 // allocate the raw io buffer. Make it one element larger to make sure we can access the last byte via int32_t*
134 _ioBuffer.resize(
135 static_cast<size_t>(_registerInfo.elementPitchBits) / 8 * _registerInfo.nElements / sizeof(int32_t) + 1);
136 }
137
138 /********************************************************************************************************************/
139
140 template<class UserType>
142 assert(_registerInfo.elementPitchBits % 8 == 0);
143
144 auto nbt = _registerInfo.elementPitchBits / 8 * _registerInfo.nElements;
145 nbt = ((nbt - 1) / 4 + 1) * 4; // round up to multiple of 4 bytes
146
147 _ioDevice->read(_registerInfo.bar, _registerInfo.address, _ioBuffer.data(), nbt);
148 }
149
150 /********************************************************************************************************************/
151
152 template<class UserType>
154 if(hasNewData) {
155 // This will call doPostReadImpl (see below) with the proper converter for each channel group
156 for(auto& group : _channelGroups) {
157 group.converterLoopHelper->doPostRead();
158 }
159
160 // it is acceptable to create the version number in post read because this accessor does not have
161 // wait_for_new_data. It is basically synchronous.
162 this->_versionNumber = {};
163
164 // we just read good data. Set validity back to ok if someone marked it faulty for writing.
165 this->_dataValidity = DataValidity::ok;
166 }
167 }
168
169 /********************************************************************************************************************/
170
171 template<class UserType>
172 template<class UserType2, typename RawType, RawConverter::SignificantBitsCase sc, RawConverter::FractionalCase fc,
173 bool isSigned>
175 RawConverter::Converter<UserType2, RawType, sc, fc, isSigned> converter, size_t channelGroupId) {
176 static_assert(std::is_same_v<UserType, UserType2>);
177 if constexpr(!std::is_same_v<RawType, ChimeraTK::Void>) {
178 auto& group = _channelGroups[channelGroupId];
179
180 // initialise the cooked iterators with beginning of the buffer
181 for(auto& channel : group.channels) {
182 channel.cookedIterator = buffer_2D[channel.index].begin();
183 }
184
185 // Using a raw pointer for the raw buffer... We need to move the pointer byte-wise and will do a memcpy later,
186 // hence this is actually safe.
187 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
188 auto* rawIterator = reinterpret_cast<std::byte*>(_ioBuffer.data()) + group.startOffset;
189
190 for(size_t i = 0; i < this->getNumberOfSamples(); ++i) {
191 for(auto& channel : group.channels) {
192 // acquire the raw value safely from the buffer, independent of alignment etc.
193 RawType rawValue;
194 std::memcpy(&rawValue, rawIterator, sizeof(RawType));
195
196 // perform conversion and store to cooked buffer
197 *channel.cookedIterator = converter.toCooked(rawValue);
198
199 // increment iterators
200 ++channel.cookedIterator;
201 rawIterator += channel.offsetToNext;
202 }
203 }
204 }
205 }
206
207 /********************************************************************************************************************/
208
209 template<class UserType>
211 assert(_registerInfo.elementPitchBits % 8 == 0);
212
213 auto nbt = _registerInfo.elementPitchBits / 8 * _registerInfo.nElements;
214 nbt = ((nbt - 1) / 4 + 1) * 4; // round up to multiple of 4 bytes
215
216 _ioDevice->write(_registerInfo.bar, _registerInfo.address, &(_ioBuffer[0]), nbt);
217 return false;
218 }
219
220 /********************************************************************************************************************/
221
222 template<class UserType>
224 if(!_ioDevice->isOpen()) {
225 throw ChimeraTK::logic_error("Device not opened.");
226 }
227
228 for(auto& group : _channelGroups) {
229 group.converterLoopHelper->doPreWrite();
230 }
231 }
232
233 /********************************************************************************************************************/
234
235 template<class UserType>
236 template<class UserType2, typename RawType, RawConverter::SignificantBitsCase sc, RawConverter::FractionalCase fc,
237 bool isSigned>
239 RawConverter::Converter<UserType2, RawType, sc, fc, isSigned> converter, size_t channelGroupId) {
240 if constexpr(!std::is_same_v<RawType, ChimeraTK::Void>) {
241 auto& group = _channelGroups[channelGroupId];
242
243 // Vector with pairs of source iterator and destination iterator
244 for(auto& channel : group.channels) {
245 channel.cookedIterator = buffer_2D[channel.index].begin();
246 }
247
248 // Using a raw pointer for the raw buffer... We need to move the pointer byte-wise and will do a memcpy later,
249 // hence this is actually safe.
250 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
251 auto* rawIterator = reinterpret_cast<std::byte*>(_ioBuffer.data()) + group.startOffset;
252
253 for(size_t i = 0; i < this->getNumberOfSamples(); ++i) {
254 for(auto& channel : group.channels) {
255 RawType rawValue = converter.toRaw(*channel.cookedIterator);
256
257 // store the raw value safely to the buffer, independent of alignment etc.
258 std::memcpy(rawIterator, &rawValue, sizeof(RawType));
259
260 // increment iterators
261 ++channel.cookedIterator;
262 rawIterator += channel.offsetToNext;
263 }
264 }
265 }
266 }
267
268 /********************************************************************************************************************/
269
271} // namespace ChimeraTK
#define INSTANTIATE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(TemplateClass)
N-dimensional register accessor.
std::vector< std::vector< UserType > > buffer_2D
Buffer of converted data elements.
Implementation of the NDRegisterAccessor for NumericAddressedBackends for multiplexd 2D registers.
void doPostReadImpl(RawConverter::Converter< UserType2, RawType, sc, fc, isSigned > converter, size_t channelGroupId)
void doReadTransferSynchronously() override
Implementation version of readTransfer() for synchronous reads.
void doPostRead(TransferType type, bool hasNewData) override
Backend specific implementation of postRead().
bool doWriteTransfer(ChimeraTK::VersionNumber versionNumber) override
Implementation version of writeTransfer().
void doPreWriteImpl(RawConverter::Converter< UserType2, RawType, sc, fc, isSigned > converter, size_t channelGroupId)
void doPreWrite(TransferType type, VersionNumber versionNumber) override
Backend specific implementation of preWrite().
NumericAddressedBackendMuxedRegisterAccessor(const RegisterPath &registerPathName, size_t numberOfElements, size_t elementsOffset, const boost::shared_ptr< DeviceBackend > &_backend)
Converter class for conversions from raw to cooked values.
UserType toCooked(RawType rawValue)
RawType toRaw(UserType cookedValue)
Class to store a register path name.
Class for generating and holding version numbers without exposing a numeric representation.
Exception thrown when a logic error has occured.
Definition Exception.h:51
This header collects some conversion routines which should be useful both for ApplicationCore Python ...
TransferType
Used to indicate the applicable operation on a Transferelement.
std::string to_string(const std::string &v)