ChimeraTK-DeviceAccess 03.26.00
Loading...
Searching...
No Matches
RawConverter.h
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#pragma once
4
6
7#include <cstdint>
8#include <tuple>
9
11
12 /********************************************************************************************************************/
13
14 // Note: values must match index in SignificantBitsCaseTypes tuple
15 enum class SignificantBitsCase { bit8 = 0, bit16, bit32, bit64, generic };
16
17 /********************************************************************************************************************/
18
20
21 /********************************************************************************************************************/
22
39 template<typename UserType, typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned>
40 class Converter {
41 public:
43
44 UserType toCooked(RawType rawValue);
45
46 RawType toRaw(UserType cookedValue);
47
48 private:
49 RawType _signBitMask, _usedBitMask, _unusedBitMask;
50 UserType _minCookedValues, _maxCookedValues;
51 RawType _minRawValue, _maxRawValue;
52
53 // The PromotedRawType has the same width as the RawType but the signedness according to isSigned. We will store the
54 // "promoted" raw value in it, i.e. the arbitrary bit width of the raw has been changed into a proper CPU data type.
55 using PromotedRawType = std::conditional_t<isSigned, std::make_signed_t<RawType>, RawType>;
56
57 // Use double as intermediate conversion target, unless user has requested float (to avoid unnecessary conversion
58 // to float via double).
59 using FloatIntermediate = std::conditional_t<std::is_same_v<UserType, float>, float, double>;
60
61 FloatIntermediate _conversionFactor, _inverseConversionFactor;
62
63 // We need the negative fractional bits as a positive value (needed for the bit shift). This field is unused unless
64 // fc == FractionalCase::fixedNegative.
65 uint32_t _nNegativeFractionalBits;
66 };
67
68 /********************************************************************************************************************/
69
74 public:
75 ConverterLoopHelper(const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex);
76
77 virtual ~ConverterLoopHelper() = default;
78
93 virtual void doPostRead() = 0;
94
100 virtual void doPreWrite() = 0;
101
106 template<typename UserType, typename Accessor>
107 static std::unique_ptr<ConverterLoopHelper> makeConverterLoopHelper(
108 const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex, Accessor& accessor);
109
110 protected:
112 const size_t _channelIndex;
113 };
114
115 /********************************************************************************************************************/
116
117 template<typename UserType, typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned,
118 typename Accessor>
120 public:
123
124 void doPostRead() override;
125
126 void doPreWrite() override;
127
128 private:
130 Accessor& _accessor;
131 };
132
133 /********************************************************************************************************************/
134
140 template<typename UserType, typename RawType, typename FUN>
141 void withConverter(const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex, FUN&& fun);
142
143 /********************************************************************************************************************/
144 /********************************************************************************************************************/
145
146 /* Note: Only implementations and helper classes/functions below this point */
147
148 /********************************************************************************************************************/
149 /********************************************************************************************************************/
150
151 namespace detail {
152
153 /******************************************************************************************************************/
154
155 // Note: order must match SignificantBitsCase enum
156 using SignificantBitsCaseTypes = std::tuple<uint8_t, uint16_t, uint32_t, uint64_t>;
157
158 /******************************************************************************************************************/
159
160 constexpr int getMinWidthsForFractionalCase(const FractionalCase& fc) {
161 switch(fc) {
163 return 32;
164 default:
165 return 8;
166 }
167 }
168
169 /******************************************************************************************************************/
170
171 template<typename RawType, typename F>
172 void callForSignificantBitsCase(const ChimeraTK::NumericAddressedRegisterInfo::ChannelInfo& info, F&& fun) {
173 switch(info.width) {
174 case 8:
175 if constexpr(std::numeric_limits<RawType>::digits >= 8) {
176 std::forward<F>(fun).template operator()<SignificantBitsCase::bit8>();
177 return;
178 }
179 case 16:
180 if constexpr(std::numeric_limits<RawType>::digits >= 16) {
181 std::forward<F>(fun).template operator()<SignificantBitsCase::bit16>();
182 return;
183 }
184 case 32:
185 if constexpr(std::numeric_limits<RawType>::digits >= 32) {
186 std::forward<F>(fun).template operator()<SignificantBitsCase::bit32>();
187 return;
188 }
189 case 64:
190 if constexpr(std::numeric_limits<RawType>::digits >= 64) {
191 std::forward<F>(fun).template operator()<SignificantBitsCase::bit64>();
192 return;
193 }
194 }
195 std::forward<F>(fun).template operator()<SignificantBitsCase::generic>();
196 }
197
198 /******************************************************************************************************************/
199
200 template<typename RawType, typename F>
201 void callForFractionalCase(const ChimeraTK::NumericAddressedRegisterInfo::ChannelInfo& info, F&& fun) {
202 switch(info.dataType) {
204 if(info.nFractionalBits == 0) {
205 std::forward<F>(fun).template operator()<FractionalCase::integer>();
206 return;
207 }
208 else if(info.nFractionalBits > 0) {
209 std::forward<F>(fun).template operator()<FractionalCase::fixedPositive>();
210 return;
211 }
212 else if(info.nFractionalBits < 0) {
213 std::forward<F>(fun).template operator()<FractionalCase::fixedNegative>();
214 return;
215 }
216 break;
218 if constexpr(std::numeric_limits<RawType>::digits >= 32) {
219 std::forward<F>(fun).template operator()<FractionalCase::ieee754_32>();
220 return;
221 }
222 break;
225 break;
226 }
227 throw std::logic_error("NOT IMPLEMENTED");
228 }
229
230 /******************************************************************************************************************/
231
232 template<typename UserType, typename RawType, typename F>
233 void callWithConverterParamsFixedRaw(
234 const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex, F&& fun) {
235 // get number of bits from info and determine SignificantBitsCase
236 detail::callForSignificantBitsCase<RawType>(info.channels[channelIndex], [&]<SignificantBitsCase sc> {
237 // get number of fractional bits from info and determine FractionalCase
238 detail::callForFractionalCase<RawType>(info.channels[channelIndex], [&]<FractionalCase fc> {
239 if constexpr(std::numeric_limits<RawType>::digits >= detail::getMinWidthsForFractionalCase(fc)) {
240 if constexpr(fc == FractionalCase::ieee754_32) {
241 // special case: IEEE754 is always signed, so we can avoid an additional code instance
242 std::forward<F>(fun).template operator()<RawType, sc, fc, true>();
243 }
244 else {
245 // Fractional/Integers: distinguish signed/unsigned and do the call
246 if(info.channels[channelIndex].signedFlag) {
247 std::forward<F>(fun).template operator()<RawType, sc, fc, true>();
248 }
249 else {
250 std::forward<F>(fun).template operator()<RawType, sc, fc, false>();
251 }
252 }
253 }
254 else {
255 throw ChimeraTK::logic_error(
256 std::format("Requested data type does not fit into the raw data width for register '{}', channel {}.",
257 std::string(info.getRegisterName()), channelIndex));
258 }
259 });
260 });
261 }
262
263 /******************************************************************************************************************/
264
265 template<typename UserType, typename F>
266 void callWithConverterParams(const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex, F&& fun) {
267 // Special case for FundamentalType::nodata, i.e. RawType = ChimeraTK::Void. Use full template specialisation.
268 // This special case saves us a couple of unnecessary code instantiations and hence speeds up compile time.
270 std::forward<F>(fun)
272 return;
273 }
274
275 // get RawType from info
276 callForRawType(info.channels[channelIndex].getRawType(), [&](auto x) {
277 using RawType = std::make_unsigned_t<decltype(x)>;
278 if constexpr(std::is_same_v<UserType, ChimeraTK::Void>) {
279 // Special case for UserType = ChimeraTK::Void. Use full template specialisation. We do not care about
280 // details in the conversion, since there is only one possible value. This special case saves us a couple of
281 // unnecessary code instantiations and hence speeds up compile time.
282 std::forward<F>(fun)
283 .template operator()<RawType, SignificantBitsCase::generic, FractionalCase::integer, false>();
284 }
285 else {
286 // Regular case for non-void UserTypes
287 callWithConverterParamsFixedRaw<UserType, RawType>(info, channelIndex, fun);
288 }
289 });
290 }
291
292 /******************************************************************************************************************/
293
294 template<std::size_t... Is>
295 constexpr auto makeUnusedBitMaskTable(std::index_sequence<Is...>) {
296 return std::array<uint64_t, 64>{(~uint64_t{} << Is)...};
297 }
298
299 /******************************************************************************************************************/
300
301 template<std::size_t... Is>
302 constexpr auto makeUsedBitMaskTable(std::index_sequence<Is...>) {
303 return std::array<uint64_t, 64>{(~(~uint64_t{} << Is))...};
304 }
305
306 /******************************************************************************************************************/
307
308 template<std::size_t... Is>
309 constexpr auto makeSignBitMaskTable(std::index_sequence<Is...>) {
310 return std::array<uint64_t, 64>{(Is > 0 ? (uint64_t{1} << (Is - 1)) : 0)...};
311 }
312
313 /******************************************************************************************************************/
314
315 constexpr auto unusedBitMaskTable = makeUnusedBitMaskTable(std::make_index_sequence<64>{});
316 constexpr auto usedBitMaskTable = makeUsedBitMaskTable(std::make_index_sequence<64>{});
317 constexpr auto signBitMaskTable = makeSignBitMaskTable(std::make_index_sequence<64>{});
318
319 /******************************************************************************************************************/
320
321 // returns the raw value "promoted" to the full RawType
322 template<typename PromotedRawType, typename RawType, SignificantBitsCase significantBitsCase>
323 constexpr PromotedRawType interpretArbitraryBitInteger(
324 RawType signBitMask, RawType usedBitMask, RawType unusedBitMask, RawType rawValue) {
325 static_assert(std::is_integral_v<RawType>);
326 static_assert(!std::is_signed_v<RawType>);
327
328 if constexpr(significantBitsCase == SignificantBitsCase::generic) {
329 if constexpr(std::is_signed_v<PromotedRawType>) {
330 if(!(rawValue & signBitMask)) { // sign bit not set: force unused bits to zero
331 return PromotedRawType(rawValue & usedBitMask);
332 }
333 // sign bit set: force unused bits to one
334 return PromotedRawType(rawValue | unusedBitMask);
335 }
336 else {
337 // unsigned value: force unused bits to zero
338 return PromotedRawType(rawValue & usedBitMask);
339 }
340 }
341 else {
342 // Here we make use of the ability of the CPU to actually understand a type with the given number of bits, so we
343 // can promote it to our PromotedRawType with standard cast operations. Keep in mind that this has nothing to do
344 // with the width of the RawType. Example: RawType is uint32_t but we have 8 significant bits (including sign
345 // bit). We will cast the raw value into (u)int8_t (depending on the selected signedness) and then into the
346 // PromotedRawType. This cuts away any extra bits and properly takes care of the sign.
347 using UnsignedType = std::tuple_element_t<int(significantBitsCase), SignificantBitsCaseTypes>;
348 using SignedType = std::make_signed_t<UnsignedType>;
349 if constexpr(std::is_signed_v<PromotedRawType>) {
350 return PromotedRawType(SignedType(rawValue));
351 }
352 else {
353 return PromotedRawType(UnsignedType(rawValue));
354 }
355 }
356 }
357
358 /******************************************************************************************************************/
359
360 } // namespace detail
361
362 /********************************************************************************************************************/
363 /********************************************************************************************************************/
364
365 template<typename UserType, typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned>
368 : _signBitMask(isSigned ? detail::signBitMaskTable[info.width] : 0),
369 _usedBitMask(detail::usedBitMaskTable[info.width]), _unusedBitMask(detail::unusedBitMaskTable[info.width]) {
370 // sanity check for parameters
372 constexpr uint32_t maxRawWidth = std::numeric_limits<uint64_t>::digits;
373 if(info.width > maxRawWidth) {
375 std::format("RawConverter cannot deal with a bit width of {} > {}.", info.width, maxRawWidth));
376 }
377 if(info.nFractionalBits > int32_t(info.width)) {
378 throw ChimeraTK::logic_error(std::format(
379 "RawConverter cannot deal with {} fractional bits (larger than total width).", info.nFractionalBits));
380 }
381 if(info.nFractionalBits < -int32_t(maxRawWidth - info.width)) {
382 throw ChimeraTK::logic_error(std::format(
383 "RawConverter cannot deal with {} fractional bits (too negative, result doesn't fit in {} bits).",
384 info.nFractionalBits, maxRawWidth));
385 }
386 }
387 // Fixed point conversion uses either floating-point conversion factors or bit shift
389 // This is used for toRaw conversions, where we never do bit shifts to get proper rounding
390 _inverseConversionFactor = std::pow(FloatIntermediate(2), info.nFractionalBits);
391 }
392 if constexpr(fc == FractionalCase::fixedPositive ||
393 (fc == FractionalCase::fixedNegative && std::is_floating_point_v<UserType>)) {
394 // This is used for toCooked conversions when floating point is involved
395 _conversionFactor = FloatIntermediate(1.) / _inverseConversionFactor;
396 }
397 else if constexpr(fc == FractionalCase::fixedNegative) {
398 // This is used for toCooked conversions when we can to a bit shift (int-to-int with negative fractional bits)
399 _nNegativeFractionalBits = -info.nFractionalBits;
400 }
401
402 // toRaw conversion needs to know minimum and maximum value range for clamping
403 // This must come last, since we already call toCooked (which does not need these values for clamping)
406 if constexpr(isSigned) {
407 _maxRawValue = _usedBitMask ^ _signBitMask;
408 _minRawValue = _signBitMask;
409 }
410 else {
411 _maxRawValue = _usedBitMask;
412 _minRawValue = 0;
413 }
414 }
415 else {
416 static_assert(fc == FractionalCase::ieee754_32);
417 _maxRawValue = RawType(std::bit_cast<uint32_t>(std::numeric_limits<float>::max()));
418 _minRawValue = RawType(std::bit_cast<uint32_t>(std::numeric_limits<float>::lowest()));
419 }
420 _maxCookedValues = toCooked(_maxRawValue);
421 _minCookedValues = toCooked(_minRawValue);
422 }
423
424 /********************************************************************************************************************/
425
426 template<typename UserType, typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned>
428 auto promotedRawValue = detail::interpretArbitraryBitInteger<PromotedRawType, RawType, sc>(
429 _signBitMask, _usedBitMask, _unusedBitMask, rawValue);
430
431 if constexpr(fc == FractionalCase::integer) {
432 return numericToUserType<UserType>(promotedRawValue);
433 }
434 else if constexpr(fc == FractionalCase::fixedPositive ||
435 (fc == FractionalCase::fixedNegative && std::is_floating_point_v<UserType>)) {
436 // Positive fractional bits will always convert through an intermediate float, to have proper rounding.
437 // Negative fractional bits can use the same code path efficiently, if the UserType is floating point.
438 return numericToUserType<UserType>(FloatIntermediate(promotedRawValue) * _conversionFactor);
439 }
440 else if constexpr(fc == FractionalCase::fixedNegative) {
441 // In signed case, make sure to fill up the leading 1s if negative.
442 // @TODO Write test for signed negative case!!
443 using IntIntermediate = std::conditional_t<isSigned, int64_t, uint64_t>;
444 auto intermediateUnsigned = uint64_t(IntIntermediate(promotedRawValue));
445 intermediateUnsigned <<= _nNegativeFractionalBits;
446 return numericToUserType<UserType>(IntIntermediate(intermediateUnsigned));
447 }
448 else {
449 static_assert(fc == FractionalCase::ieee754_32);
450 static_assert(std::numeric_limits<RawType>::digits >= 32);
451 static_assert(std::numeric_limits<float>::is_iec559);
452 return numericToUserType<UserType>(std::bit_cast<float>(uint32_t(promotedRawValue)));
453 }
454 }
455
456 /********************************************************************************************************************/
457
458 template<typename UserType, typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned>
460 // Do a range check first. The later overflow check in the conversion is not
461 // sufficient, since we can have non-standard word sizes like 12 bits.
462 if constexpr(!std::is_same_v<UserType, ChimeraTK::Void>) {
463 if(cookedValue < _minCookedValues) {
464 return _minRawValue;
465 }
466 if(cookedValue > _maxCookedValues) {
467 return _maxRawValue;
468 }
469 }
470
471 PromotedRawType promotedRawValue;
472
473 if constexpr(fc == FractionalCase::integer) {
474 promotedRawValue = userTypeToNumeric<PromotedRawType>(cookedValue);
475 }
476 else if constexpr(fc == FractionalCase::fixedPositive || fc == FractionalCase::fixedNegative) {
477 // All fractional bit cases will always convert through an intermediate float, to have proper rounding.
478 promotedRawValue = userTypeToNumeric<PromotedRawType>(
479 userTypeToNumeric<FloatIntermediate>(cookedValue) * _inverseConversionFactor);
480 }
481 else {
482 static_assert(fc == FractionalCase::ieee754_32);
483 static_assert(std::numeric_limits<RawType>::digits >= 32);
484 static_assert(std::numeric_limits<float>::is_iec559);
485 promotedRawValue = PromotedRawType(std::bit_cast<uint32_t>(userTypeToNumeric<float>(cookedValue)));
486 }
487
488 return RawType(promotedRawValue) & _usedBitMask;
489 }
490
491 /********************************************************************************************************************/
492 /********************************************************************************************************************/
493
494 template<typename UserType, typename Accessor>
495 std::unique_ptr<ConverterLoopHelper> ConverterLoopHelper::makeConverterLoopHelper(
496 const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex, Accessor& accessor) {
497 std::unique_ptr<ConverterLoopHelper> rv;
498
499 detail::callWithConverterParams<UserType>(
500 info, channelIndex, [&]<typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned> {
501 Converter<UserType, RawType, sc, fc, isSigned> converter(info.channels[channelIndex]);
502 rv = std::make_unique<ConverterLoopHelperImpl<UserType, RawType, sc, fc, isSigned, Accessor>>(
503 info, channelIndex, converter, accessor);
504 });
505
506 assert(rv != nullptr);
507 return rv;
508 }
509
510 /********************************************************************************************************************/
511
513 const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex)
514 : _info(info), _channelIndex(channelIndex) {}
515
516 /********************************************************************************************************************/
517
518 template<typename UserType, typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned,
519 typename Accessor>
521 const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex,
523 : ConverterLoopHelper(info, channelIndex), _converter(converter), _accessor(accessor) {}
524
525 /********************************************************************************************************************/
526
527 template<typename UserType, typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned,
528 typename Accessor>
530 _accessor.template doPostReadImpl<UserType, RawType, sc, fc, isSigned>(_converter, _channelIndex);
531 }
532
533 /********************************************************************************************************************/
534
535 template<typename UserType, typename RawType, SignificantBitsCase sc, FractionalCase fc, bool isSigned,
536 typename Accessor>
538 _accessor.template doPreWriteImpl<UserType, RawType, sc, fc, isSigned>(_converter, _channelIndex);
539 }
540
541 /********************************************************************************************************************/
542
543 template<typename UserType, typename RawType, typename FUN>
544 void withConverter(const ChimeraTK::NumericAddressedRegisterInfo& info, size_t channelIndex, FUN&& fun) {
545 detail::callWithConverterParamsFixedRaw<UserType, RawType>(
546 info, channelIndex, [&]<typename RawTypeAgain, SignificantBitsCase sc, FractionalCase fc, bool isSigned> {
547 static_assert(std::is_same_v<RawTypeAgain, RawType>);
548 Converter<UserType, RawType, sc, fc, isSigned> converter(info.channels[channelIndex]);
549 std::forward<FUN>(fun).operator()(converter);
550 });
551 }
552
553 /********************************************************************************************************************/
554 /********************************************************************************************************************/
555
559 template<typename UserType, typename RawType>
560 requires(std::is_same_v<UserType, ChimeraTK::Void> || std::is_same_v<RawType, ChimeraTK::Void>)
562 public:
564
565 UserType toCooked(RawType rawValue);
566
567 RawType toRaw(UserType cookedValue);
568 };
569
570 /********************************************************************************************************************/
571
572 template<typename UserType, typename RawType>
573 requires(std::is_same_v<UserType, ChimeraTK::Void> || std::is_same_v<RawType, ChimeraTK::Void>)
578
579 /********************************************************************************************************************/
580
581 template<typename UserType, typename RawType>
582 requires(std::is_same_v<UserType, ChimeraTK::Void> || std::is_same_v<RawType, ChimeraTK::Void>)
586
587 /********************************************************************************************************************/
588
589} // namespace ChimeraTK::RawConverter
FundamentalType fundamentalType() const
Get the fundamental data type.
std::vector< ChannelInfo > channels
Define per-channel information (bit interpretation etc.), 1D/scalars have exactly one entry.
Converter class for conversions from raw to cooked values.
UserType toCooked(RawType rawValue)
RawType toRaw(UserType cookedValue)
Abstract base class to implement erasure of the exact Converter type.
virtual void doPostRead()=0
Call doPostReadImpl on the accessor passed to makeConverterLoopHelper().
const ChimeraTK::NumericAddressedRegisterInfo & _info
virtual void doPreWrite()=0
Call doPreWriteImpl on the accessor passed to makeConverterLoopHelper().
static std::unique_ptr< ConverterLoopHelper > makeConverterLoopHelper(const ChimeraTK::NumericAddressedRegisterInfo &info, size_t channelIndex, Accessor &accessor)
Create ConverterLoopHelper with the Converter object matching the given RegisterInfo and channel.
ConverterLoopHelper(const ChimeraTK::NumericAddressedRegisterInfo &info, size_t channelIndex)
ConverterLoopHelperImpl(const ChimeraTK::NumericAddressedRegisterInfo &info, size_t channelIndex, Converter< UserType, RawType, sc, fc, isSigned > converter, Accessor &accessor)
void doPreWrite() override
Call doPreWriteImpl on the accessor passed to makeConverterLoopHelper().
void doPostRead() override
Call doPostReadImpl on the accessor passed to makeConverterLoopHelper().
Wrapper Class for void.
Definition Void.h:15
Exception thrown when a logic error has occured.
Definition Exception.h:51
void withConverter(const ChimeraTK::NumericAddressedRegisterInfo &info, size_t channelIndex, FUN &&fun)
Create Converter matching the given register info and channel, and call the functor object passing th...
void callForRawType(const DataType &type, LAMBDATYPE lambda)
callForRawType() is similar to callForType(), just with a subset of supported data types which can be...
uint32_t width
Number of significant bits in the register.