ChimeraTK-DeviceAccess 03.29.00
Loading...
Searching...
No Matches
JsonMapFileParser.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
4#include "JsonMapFileParser.h"
5
6#include "JsonExtensions.h"
7
8#include <nlohmann/json.hpp>
9
10#include <boost/algorithm/string.hpp>
11
12#include <algorithm>
13#include <map>
14#include <string>
15
16using json = nlohmann::json;
17
18namespace ChimeraTK::detail {
19
20 /********************************************************************************************************************/
21
22 struct JsonAddressSpaceEntry;
23
24 struct JsonMapFileParser::Imp {
25 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> parse(std::ifstream& stream);
26
27 std::string fileName;
28 NumericAddressedRegisterCatalogue catalogue;
29 MetadataCatalogue metadata;
30 };
31
32 /********************************************************************************************************************/
33
34 JsonMapFileParser::JsonMapFileParser(std::string fileName) : _theImp(std::make_unique<Imp>(std::move(fileName))) {}
35
36 JsonMapFileParser::~JsonMapFileParser() = default;
37
38 /********************************************************************************************************************/
39
40 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> JsonMapFileParser::parse(std::ifstream& stream) {
41 return _theImp->parse(stream);
42 }
43
44 /********************************************************************************************************************/
45 /********************************************************************************************************************/
46
47 // map Access enum to JSON as strings. Need to redefine the strongly typed enums as old-fashioned ones....
48 enum Access {
52 accessNotSet
53 };
54 NLOHMANN_JSON_SERIALIZE_ENUM(
55 Access, {{Access::READ_ONLY, "RO"}, {Access::READ_WRITE, "RW"}, {Access::WRITE_ONLY, "WO"}})
56
57 /********************************************************************************************************************/
58
59 // map RepresentationType enum to JSON as strings
60 enum RepresentationType {
65 representationNotSet
66 };
67 NLOHMANN_JSON_SERIALIZE_ENUM(RepresentationType,
68 {{RepresentationType::FIXED_POINT, "fixedPoint"}, {RepresentationType::IEEE754, "IEEE754"},
69 {RepresentationType::VOID, "void"}, {RepresentationType::ASCII, "string"}})
70
71 /********************************************************************************************************************/
72
73 // map AddressType enum to JSON as strings
74 enum AddressType { IO, DMA, addressTypeNotSet };
75 NLOHMANN_JSON_SERIALIZE_ENUM(AddressType, {{AddressType::IO, "IO"}, {AddressType::DMA, "DMA"}})
76
77 /********************************************************************************************************************/
78
79 // Allow hex string representation of values (but still accept plain int as well)
80 struct HexValue {
81 size_t v;
82
83 // NOLINTNEXTLINE(readability-identifier-naming)
84 friend void from_json(const json& j, HexValue& hv) {
85 if(j.is_string()) {
86 auto sdata = std::string(j);
87 try {
88 hv.v = std::stoll(sdata, nullptr, 0);
89 }
90 catch(std::invalid_argument& e) {
91 throw json::type_error::create(0, "Cannot parse string '" + sdata + "' as number.", &j);
92 }
93 catch(std::out_of_range& e) {
94 throw json::type_error::create(0, "Number '" + sdata + "' out of range.", &j);
95 }
96 }
97 else {
98 hv.v = j;
99 }
100 }
101
102 // NOLINTNEXTLINE(readability-identifier-naming)
103 friend void to_json(json& j, const HexValue& hv) { j = hv.v; }
104 };
105
106 /********************************************************************************************************************/
107 /********************************************************************************************************************/
108
111 struct JsonAddressSpaceEntry {
112 std::string engineeringUnit;
113 std::string description;
114 Access access{Access::accessNotSet};
115 std::vector<size_t> triggeredByInterrupt;
116 size_t numberOfElements{1};
117 size_t bytesPerElement{0};
118
119 struct DoubleBufferingInfo {
120 struct SecondAddress {
121 AddressType type{AddressType::DMA};
122 size_t channel{0};
123 HexValue offset{0};
124
125 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(SecondAddress, type, channel, offset)
126 };
127
128 SecondAddress secondaryBufferAddress;
129 std::string enableRegister;
130 std::string readBufferRegister;
131 size_t index{0};
132
133 void fill(NumericAddressedRegisterInfo& info) const {
134 info.doubleBuffer->address = secondaryBufferAddress.offset.v;
135 info.doubleBuffer->enableRegisterPath = enableRegister;
136 info.doubleBuffer->inactiveBufferRegisterPath = readBufferRegister;
137 info.doubleBuffer->index = index;
138 }
139
140 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
141 DoubleBufferingInfo, secondaryBufferAddress, enableRegister, readBufferRegister, index)
142 };
143 std::optional<DoubleBufferingInfo> doubleBuffering;
144
145 struct Address {
146 AddressType type{AddressType::IO};
147 size_t channel{0};
148 HexValue offset{std::numeric_limits<size_t>::max()};
149
150 void fill(NumericAddressedRegisterInfo& info) const {
151 assert(type != AddressType::addressTypeNotSet);
152 info.address = offset.v;
153 info.bar = channel + (type == AddressType::DMA ? 13 : 0);
154 }
155
156 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Address, type, channel, offset)
157 } address{AddressType::addressTypeNotSet};
158
159 // Basic representation without sub elements
160 struct Representation {
161 RepresentationType type{RepresentationType::FIXED_POINT};
162 uint32_t width{type != RepresentationType::representationNotSet ? 32U : 0U};
163 int32_t fractionalBits{0};
164 bool isSigned{false};
165 uint32_t bitShift{0};
166
167 void fill(NumericAddressedRegisterInfo& info, size_t offset, size_t bytesPerElem) const {
168 if(type != RepresentationType::representationNotSet) {
169 info.channels.emplace_back(8 * offset, NumericAddressedRegisterInfo::Type(type), width, fractionalBits,
170 type != RepresentationType::IEEE754 ? isSigned : true,
171 DataType("int" + std::to_string(bytesPerElem * 8)));
172 info.channels.back().bitOffset += bitShift;
173 if(bitShift != 0) {
174 info.isBitRange = true;
175 }
176 }
177 else {
178 Representation().fill(info, offset, bytesPerElem);
179 }
180 }
181
182 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Representation, type, width, fractionalBits, isSigned, bitShift)
183 } representation{RepresentationType::representationNotSet};
184
185 struct ChannelTab {
186 size_t numberOfElements{0};
187 size_t pitch{0};
188
189 struct Channel {
190 std::string engineeringUnit;
191 std::string description;
192 size_t offset;
193 size_t bytesPerElement{4};
194 Representation representation{};
195
197 void fill(NumericAddressedRegisterInfo& info) const { representation.fill(info, offset, bytesPerElement); }
198
199 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
200 Channel, engineeringUnit, description, offset, bytesPerElement, representation)
201 };
202
203 // The channels, sorted by byte offset so that per-channel information (and thus the channel index of a
204 // 2D accessor) follows the natural memory order, independent of the lexically sorted map key. Returns the
205 // channel names together with the pointers so both the plain fill and the slice creation can use it.
206 [[nodiscard]] std::vector<std::pair<std::string, const Channel*>> channelsInOffsetOrder() const {
207 std::vector<std::pair<std::string, const Channel*>> result;
208 result.reserve(channels.size());
209 for(const auto& [channelName, channel] : channels) {
210 result.emplace_back(channelName, &channel);
211 }
212 std::ranges::sort(result, [](const auto& a, const auto& b) { return a.second->offset < b.second->offset; });
213 return result;
214 }
215
216 std::map<std::string, Channel> channels;
217
218 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ChannelTab, numberOfElements, pitch, channels)
219 };
220 std::vector<ChannelTab> channelTabs;
221
222 void fill(NumericAddressedRegisterInfo& info, const std::string& name, const RegisterPath& parentName,
223 bool addressSetByParent) const {
224 info.pathName = parentName / name;
225 info.pathName.setAltSeparator(".");
226
227 if(triggeredByInterrupt.empty()) {
228 if(access != Access::accessNotSet) {
229 info.registerAccess = NumericAddressedRegisterInfo::Access(access);
230 }
231 else if(!addressSetByParent) {
233 }
234 }
235 else {
236 if(access != Access::accessNotSet) {
238 "Register " + info.pathName + ": 'access' and 'triggeredByInterrupt' are mutually exclusive.");
239 }
240 info.interruptId = triggeredByInterrupt;
242 }
243
244 if(representation.type != RepresentationType::VOID) {
245 if(address.type != AddressType::addressTypeNotSet) {
246 address.fill(info);
247 if(channelTabs.empty()) {
248 auto bPerElem = (bytesPerElement != 0 ? bytesPerElement : 4); // create default if not set
249 info.elementPitchBits = bPerElem * 8;
250 info.nElements = numberOfElements;
251 representation.fill(info, 0, bPerElem);
252 }
253 else {
254 if(channelTabs[0].channels.empty()) {
255 throw ChimeraTK::logic_error("Empty channel definition in register " + info.pathName);
256 }
257 info.elementPitchBits = channelTabs[0].pitch * 8;
258 info.nElements = channelTabs[0].numberOfElements;
259 // Iterate the channels sorted by byte offset (see ChannelTab::channelsInOffsetOrder) so the per-channel
260 // information (and hence the channel index of the 2D accessor) stays in the natural memory order.
261 for(const auto& [channelName, channel] : channelTabs[0].channelsInOffsetOrder()) {
262 (void)channelName; // the channel name is the map key; the channel data carries its own offset
263 channel->fill(info);
264 }
265 }
266 }
267 else if(addressSetByParent) {
268 if(channelTabs.empty()) {
269 if(representation.type == RepresentationType::representationNotSet) {
270 throw ChimeraTK::logic_error("Representation not set for register " + parentName / name +
271 " which inherited the address from parent!");
272 }
273 // If bytesPerElement has not been set in the json file, take it from parent info
274 representation.fill(info, 0, (bytesPerElement != 0 ? bytesPerElement : info.elementPitchBits / 8));
275 }
276 else {
277 throw ChimeraTK::logic_error("Address must be set for entries in channel tabs: register " + info.pathName);
278 }
279 }
280 else {
281 throw ChimeraTK::logic_error("Address not set but representation given in register " + parentName / name);
282 }
283 }
284 else {
285 // VOID registers have no address — they are pure interrupt sources
286 if(address.type != AddressType::addressTypeNotSet) {
287 throw ChimeraTK::logic_error("Address is set for void-typed register " + info.pathName);
288 }
289 if(triggeredByInterrupt.empty()) {
291 "Void-typed register " + parentName / name + " needs 'triggeredByInterrupt' entry.");
292 }
293 info.nElements = 0;
294 info.dataDescriptor = DataDescriptor{DataDescriptor::FundamentalType::nodata};
295 info.interruptId = triggeredByInterrupt;
297 info.channels.clear();
298 info.channels.emplace_back(0, NumericAddressedRegisterInfo::Type::VOID, 0, 0, false);
299 }
300
301 if(doubleBuffering) {
302 info.doubleBuffer.emplace();
303 doubleBuffering->fill(info);
304 }
305 else {
306 info.doubleBuffer.reset();
307 }
308
309 info.description = description;
310 info.engineeringUnit = engineeringUnit;
311 }
312
313 std::map<std::string, JsonAddressSpaceEntry> children;
314
315 void addInfos(NumericAddressedRegisterCatalogue& catalogue, const std::string& name, const RegisterPath& parentName,
316 bool addressSetByParent) const {
317 if(name.empty()) {
318 throw ChimeraTK::logic_error("Entry in module " + parentName + " has no name.");
319 }
320 if(address.type != AddressType::addressTypeNotSet) {
321 // New address entry. Don't use parent information
322 NumericAddressedRegisterInfo my;
323 my.channels.clear(); // default constructor already creates a channel with default settings...
324 fill(my, name, parentName, addressSetByParent);
325 my.computeDataDescriptor();
326 catalogue.addRegister(my);
327 if(!channelTabs.empty()) {
328 // create one register entry per named channel of the first channel tab: a read-only 1D slice of the
329 // 2D register. The channel's byte offset is folded into the address (so bitOffset == 0), and the full
330 // element pitch is kept as the stride between samples. Iterate sorted by byte offset (see
331 // ChannelTab::channelsInOffsetOrder) so the created slice registers follow the natural memory order.
332 for(const auto& [channelName, channel] : channelTabs[0].channelsInOffsetOrder()) {
333 RegisterPath slicePath = my.pathName / channelName;
334 slicePath.setAltSeparator(".");
335 // skip a channel whose slice path would collide with an already created slice (e.g. a
336 // bit-field channel split into multiple entries carrying the same name)
337 if(catalogue.hasRegister(slicePath)) {
338 continue;
339 }
340 const auto& rep = channel->representation;
341 NumericAddressedRegisterInfo::ChannelInfo ci{rep.bitShift, // bitOffset within the channel element
342 NumericAddressedRegisterInfo::Type(rep.type), rep.width, rep.fractionalBits,
343 rep.type != RepresentationType::IEEE754 ? rep.isSigned : true,
344 DataType("int" + std::to_string(channel->bytesPerElement * 8))};
345 // A channel slice of a non-interrupt 2D register is read-only: writing to a single channel of a 2D
346 // register would require a read-modify-write cycle across the channels, which is deliberately not
347 // supported. A slice of an interrupt-driven 2D register additionally advertises wait_for_new_data,
348 // since the whole 2D register (including all its channel slices) updates with the same interrupt.
349 auto sliceAccessType = (my.registerAccess == NumericAddressedRegisterInfo::Access::INTERRUPT) ?
352 NumericAddressedRegisterInfo slice(slicePath, my.bar, my.address + channel->offset, my.nElements,
353 my.elementPitchBits, {ci}, sliceAccessType, my.interruptId, my.doubleBuffer);
354 slice.isBitRange = (rep.bitShift != 0);
355 slice.computeDataDescriptor();
356 slice.engineeringUnit = channel->engineeringUnit;
357 slice.description = channel->description;
358 catalogue.addRegister(slice);
359 }
360 }
361 if(doubleBuffering.has_value()) {
362 // Create the .buf0 register as a copy of the main one
363 NumericAddressedRegisterInfo buf0Register = my;
364 buf0Register.pathName = my.pathName + "/BUF0";
365 buf0Register.doubleBuffer.reset(); // it's a simple view of the buffer
366 buf0Register.registerAccess = NumericAddressedRegisterInfo::Access::READ_ONLY;
367 buf0Register.computeDataDescriptor();
368 catalogue.addRegister(buf0Register);
369 NumericAddressedRegisterInfo buf1Register = my;
370 buf1Register.pathName = my.pathName + "/BUF1";
371 buf1Register.doubleBuffer.reset(); // it's a simple view of the buffer
372 buf1Register.address = doubleBuffering->secondaryBufferAddress.offset.v;
373 buf1Register.registerAccess = NumericAddressedRegisterInfo::Access::READ_ONLY;
374 // buf1Register.bar = doubleBuffering->secondBufferAddress.channel +
375 // (doubleBuffering->secondaryBufferAddress.type == AddressType::DMA ? 13 : 0);
376 buf1Register.computeDataDescriptor();
377 catalogue.addRegister(buf1Register);
378 }
379 }
380 else if(representation.type != RepresentationType::representationNotSet) {
381 // take over parent address (except void interrupt registers which don't have an address)
382 auto my = catalogue.getBackendRegister(parentName);
383 my.channels.clear(); // will be refilled from representation
384 fill(my, name, parentName, addressSetByParent); // only updates the name and the representation
385 my.computeDataDescriptor();
386 catalogue.addRegister(my);
387 }
388
389 for(const auto& [childName, child] : children) {
390 child.addInfos(catalogue, childName, parentName / name,
391 addressSetByParent || (address.type != AddressType::addressTypeNotSet));
392 }
393 }
394
395 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(JsonAddressSpaceEntry, engineeringUnit, description, access,
396 triggeredByInterrupt, numberOfElements, bytesPerElement, address, representation, children, channelTabs,
397 doubleBuffering)
398 };
399
400 /********************************************************************************************************************/
401
402 struct InterruptHandlerEntry {
403 struct Controller {
404 std::string path;
405 std::set<std::string> options;
406 int version{1};
407 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Controller, path, options, version)
408 } INTC;
409
410 std::map<std::string, InterruptHandlerEntry> subhandler;
411
412 void fill(const std::vector<size_t>& intId, MetadataCatalogue& metadata) const {
413 if(!intId.empty()) {
414 json jsonIntId;
415 jsonIntId = intId;
416 json jsonController;
417 jsonController = INTC;
418 metadata.addMetadata("!" + jsonIntId.dump(), R"({"INTC":)" + jsonController.dump() + "}");
419 }
420
421 for(const auto& [subIntId, handler] : subhandler) {
422 std::vector<size_t> qualfiedSubIntId = intId;
423 qualfiedSubIntId.push_back(std::stoll(subIntId));
424 handler.fill(qualfiedSubIntId, metadata);
425 }
426 }
427
428 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(InterruptHandlerEntry, INTC, subhandler)
429 };
430
431 /********************************************************************************************************************/
432 /********************************************************************************************************************/
433
434 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> JsonMapFileParser::Imp::parse(std::ifstream& stream) {
435 // read and parse JSON data
436 try {
437 auto data = json::parse(stream);
438
439 std::map<std::string, JsonAddressSpaceEntry> addressSpace = data.at("addressSpace");
440 for(const auto& [addressSpaceName, entry] : addressSpace) {
441 entry.addInfos(catalogue, addressSpaceName, "/", /*addressSetByParent=*/false);
442 }
443
444 // Scan the catalogue for bit ranges.
445 // Afterwards, scan again for registers which have bit shift 0, a width smaller than their element size and that
446 // share their starting address with a bit range. They have to become bit ranges as well.
447 std::set<std::pair<uint64_t, uint64_t>> addressesWithBitRange;
448 for(auto& reg : catalogue) {
449 if(reg.isBitRange) {
450 addressesWithBitRange.insert({reg.bar, reg.address});
451 }
452 }
453 if(addressesWithBitRange.size()) {
454 for(auto& reg : catalogue) {
455 if((reg.channels.size() == 1) && (reg.channels[0].bitOffset == 0) &&
456 (reg.channels[0].width < reg.elementPitchBits)) {
457 if(addressesWithBitRange.find({reg.bar, reg.address}) != addressesWithBitRange.end()) {
458 reg.isBitRange = true;
459 }
460 }
461 }
462 }
463
464 for(const auto& entry : data.at("metadata").items()) {
465 if(entry.key().empty()) {
467 "Error parsing JSON map file '" + fileName + "': Metadata key must not be empty.");
468 }
469 if(entry.key()[0] == '_') {
470 continue;
471 }
472 metadata.addMetadata(entry.key(), entry.value());
473 }
474
475 // backwards compatibility: interrupt handler description is expected to be in metadata
476 InterruptHandlerEntry interruptHandler;
477 interruptHandler.subhandler = data.at("interruptHandler");
478 interruptHandler.fill({}, metadata);
479
480 return {std::move(catalogue), std::move(metadata)};
481 }
482 catch(const ChimeraTK::logic_error& e) {
483 throw ChimeraTK::logic_error("Error parsing JSON map file '" + fileName + "': " + e.what());
484 }
485 catch(const json::exception& e) {
486 throw ChimeraTK::logic_error("Error parsing JSON map file '" + fileName + "': " + e.what());
487 }
488 }
489
490 /********************************************************************************************************************/
491
492} // namespace ChimeraTK::detail
nlohmann::json json
Access
Enum describing the access mode of the register:
Exception thrown when a logic error has occured.
Definition Exception.h:51
const char * what() const noexcept override
Return the message describing what exactly went wrong.
Definition Exception.cpp:20
std::string to_string(const std::string &v)