ChimeraTK-DeviceAccess 03.27.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 // Helper struct containing everything in ChannelInfo except the bitOffset. This helps to identify which channels
26 // can share the RawConverter.
27 struct ConverterInfo {
28 NumericAddressedRegisterInfo::Type dataType;
29 uint32_t width;
30 int32_t nFractionalBits;
31 bool signedFlag;
32 DataType rawType;
33
34 // Comparison is required to use objects as std::map key
35 auto operator<=>(const ConverterInfo& rhs) const = default;
36
37 // construct from ChannelInfo
38 explicit ConverterInfo(const NumericAddressedRegisterInfo::ChannelInfo& o)
39 : dataType(o.dataType), width(o.width), nFractionalBits(o.nFractionalBits), signedFlag(o.signedFlag),
40 rawType(o.rawType) {}
41 };
42
43 // Identify groups of channels with identical raw conversion parameters. All channels in one group can hence share
44 // the same RawConverter. This allows for some performance optimisation in the de-multiplexing and conversion step.
45 std::map<ConverterInfo, size_t> groupInfoMap;
46 for(size_t channelIndex = 0; channelIndex < _registerInfo.getNumberOfChannels(); ++channelIndex) {
47 const auto& info = _registerInfo.channels[channelIndex];
48
49 if(info.bitOffset % 8 != 0) {
50 throw ChimeraTK::logic_error("NumericAddressedBackendMuxedRegisterAccessor: elements must be byte aligned.");
51 }
52
53 // Check if a matching group already exists in the groupInfoMap
54 const auto converterInfo = ConverterInfo(info);
55 auto it = groupInfoMap.find(converterInfo);
56 if(it == groupInfoMap.end()) {
57 // No matching group exists yet, so create new ChannelGroup containing the converter
58 auto newGroupId = groupInfoMap.size();
59 groupInfoMap[converterInfo] = newGroupId;
60
61 ChannelGroup newGroup;
62 newGroup.channels.emplace_back(
63 channelIndex, 0, typename std::vector<UserType>::iterator()); // offsetToNext is set later
64 newGroup.converterLoopHelper = RawConverter::ConverterLoopHelper::makeConverterLoopHelper<UserType>(
65 _registerInfo, channelIndex, newGroupId, *this);
66 newGroup.startOffset = info.bitOffset / 8;
67
68 // Store the group and update the group map
69 _channelGroups.emplace_back(std::move(newGroup));
70 }
71 else {
72 // Matching group found: Add channel to group and update the group map
73 _channelGroups[it->second].channels.emplace_back(
74 channelIndex, 0, typename std::vector<UserType>::iterator()); // offsetToNext is set later
75 }
76 }
77 assert(_channelGroups.size() == groupInfoMap.size());
78
79 // Fill the "offsetToNext" field of the ChannelGroup::Channel struct. This cannot be done in the above loop, since
80 // we need to know already which is the next channel in the group.
81 for(auto& group : _channelGroups) {
82 assert(group.channels.size() >= 1);
83 // fill offsets for all channels in group except the last one
84 for(size_t i = 0; i < group.channels.size() - 1; ++i) {
85 auto bitOffset = _registerInfo.channels[group.channels[i + 1].index].bitOffset -
86 _registerInfo.channels[group.channels[i].index].bitOffset;
87 assert(bitOffset % 8 == 0);
88 group.channels[i].offsetToNext = bitOffset / 8;
89 }
90 // offset for the last channel in the group needs to point to the first channel in group (next sample)
91 auto lastBitOffset = _registerInfo.elementPitchBits -
92 _registerInfo.channels[group.channels.back().index].bitOffset +
93 _registerInfo.channels[group.channels.front().index].bitOffset;
94 assert(lastBitOffset % 8 == 0);
95 group.channels.back().offsetToNext = lastBitOffset / 8;
96 }
97
98 // compute effective numberOfElements
99 if(numberOfElements == 0) {
100 numberOfElements = _registerInfo.nElements;
101 }
102
103 // check number of words
104 if(numberOfElements + elementsOffset > _registerInfo.nElements) {
105 throw ChimeraTK::logic_error("Requested number of elements (" + std::to_string(numberOfElements) + " + " +
106 std::to_string(elementsOffset) + ") exceeds the size (" + std::to_string(_registerInfo.nElements) +
107 ") of the register '" + registerPathName + "'!");
108
109 throw ChimeraTK::logic_error("Requested number of elements exceeds the size of the register! Requested end: " +
110 std::to_string(numberOfElements + elementsOffset) +
111 ", register length: " + std::to_string(_registerInfo.nElements));
112 }
113
114 // update register info
115 _registerInfo.nElements = numberOfElements;
116 assert(_registerInfo.elementPitchBits % 8 == 0);
117 _registerInfo.address += elementsOffset * _registerInfo.elementPitchBits / 8;
118
119 // allocate the buffer for the converted data
120 NDRegisterAccessor<UserType>::buffer_2D.resize(_registerInfo.getNumberOfChannels());
121 for(auto& buf : NDRegisterAccessor<UserType>::buffer_2D) {
122 buf.resize(_registerInfo.nElements);
123 }
124
125 // allocate the raw io buffer. Make it one element larger to make sure we can access the last byte via int32_t*
126 _ioBuffer.resize(
127 static_cast<size_t>(_registerInfo.elementPitchBits) / 8 * _registerInfo.nElements / sizeof(int32_t) + 1);
128 }
129
130 /********************************************************************************************************************/
131
132 template<class UserType>
134 assert(_registerInfo.elementPitchBits % 8 == 0);
135
136 auto nbt = _registerInfo.elementPitchBits / 8 * _registerInfo.nElements;
137 nbt = ((nbt - 1) / 4 + 1) * 4; // round up to multiple of 4 bytes
138
139 _ioDevice->read(_registerInfo.bar, _registerInfo.address, _ioBuffer.data(), nbt);
140 }
141
142 /********************************************************************************************************************/
143
144 template<class UserType>
146 if(hasNewData) {
147 // This will call doPostReadImpl (see below) with the proper converter for each channel group
148 for(auto& group : _channelGroups) {
149 group.converterLoopHelper->doPostRead();
150 }
151
152 // it is acceptable to create the version number in post read because this accessor does not have
153 // wait_for_new_data. It is basically synchronous.
154 this->_versionNumber = {};
155
156 // we just read good data. Set validity back to ok if someone marked it faulty for writing.
157 this->_dataValidity = DataValidity::ok;
158 }
159 }
160
161 /********************************************************************************************************************/
162
163 template<class UserType>
164 template<class UserType2, typename RawType, RawConverter::SignificantBitsCase sc, RawConverter::FractionalCase fc,
165 bool isSigned>
167 RawConverter::Converter<UserType2, RawType, sc, fc, isSigned> converter, size_t channelGroupId) {
168 static_assert(std::is_same_v<UserType, UserType2>);
169 if constexpr(!std::is_same_v<RawType, ChimeraTK::Void>) {
170 auto& group = _channelGroups[channelGroupId];
171
172 // initialise the cooked iterators with beginning of the buffer
173 for(auto& channel : group.channels) {
174 channel.cookedIterator = buffer_2D[channel.index].begin();
175 }
176
177 // Using a raw pointer for the raw buffer... We need to move the pointer byte-wise and will do a memcpy later,
178 // hence this is actually safe.
179 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
180 auto* rawIterator = reinterpret_cast<std::byte*>(_ioBuffer.data()) + group.startOffset;
181
182 for(size_t i = 0; i < this->getNumberOfSamples(); ++i) {
183 for(auto& channel : group.channels) {
184 // acquire the raw value safely from the buffer, independent of alignment etc.
185 RawType rawValue;
186 std::memcpy(&rawValue, rawIterator, sizeof(RawType));
187
188 // perform conversion and store to cooked buffer
189 *channel.cookedIterator = converter.toCooked(rawValue);
190
191 // increment iterators
192 ++channel.cookedIterator;
193 rawIterator += channel.offsetToNext;
194 }
195 }
196 }
197 }
198
199 /********************************************************************************************************************/
200
201 template<class UserType>
203 assert(_registerInfo.elementPitchBits % 8 == 0);
204
205 auto nbt = _registerInfo.elementPitchBits / 8 * _registerInfo.nElements;
206 nbt = ((nbt - 1) / 4 + 1) * 4; // round up to multiple of 4 bytes
207
208 _ioDevice->write(_registerInfo.bar, _registerInfo.address, &(_ioBuffer[0]), nbt);
209 return false;
210 }
211
212 /********************************************************************************************************************/
213
214 template<class UserType>
216 if(!_ioDevice->isOpen()) {
217 throw ChimeraTK::logic_error("Device not opened.");
218 }
219
220 for(auto& group : _channelGroups) {
221 group.converterLoopHelper->doPreWrite();
222 }
223 }
224
225 /********************************************************************************************************************/
226
227 template<class UserType>
228 template<class UserType2, typename RawType, RawConverter::SignificantBitsCase sc, RawConverter::FractionalCase fc,
229 bool isSigned>
231 RawConverter::Converter<UserType2, RawType, sc, fc, isSigned> converter, size_t channelGroupId) {
232 if constexpr(!std::is_same_v<RawType, ChimeraTK::Void>) {
233 auto& group = _channelGroups[channelGroupId];
234
235 // Vector with pairs of source iterator and destination iterator
236 for(auto& channel : group.channels) {
237 channel.cookedIterator = buffer_2D[channel.index].begin();
238 }
239
240 // Using a raw pointer for the raw buffer... We need to move the pointer byte-wise and will do a memcpy later,
241 // hence this is actually safe.
242 // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast)
243 auto* rawIterator = reinterpret_cast<std::byte*>(_ioBuffer.data()) + group.startOffset;
244
245 for(size_t i = 0; i < this->getNumberOfSamples(); ++i) {
246 for(auto& channel : group.channels) {
247 RawType rawValue = converter.toRaw(*channel.cookedIterator);
248
249 // store the raw value safely to the buffer, independent of alignment etc.
250 std::memcpy(rawIterator, &rawValue, sizeof(RawType));
251
252 // increment iterators
253 ++channel.cookedIterator;
254 rawIterator += channel.offsetToNext;
255 }
256 }
257 }
258 }
259
260 /********************************************************************************************************************/
261
263} // 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
TransferType
Used to indicate the applicable operation on a Transferelement.
std::string to_string(const std::string &v)