9#include <nlohmann/json.hpp>
17using json = nlohmann::json;
19namespace ChimeraTK::detail {
23 struct JsonAddressSpaceEntry;
25 struct JsonMapFileParser::Imp {
26 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> parse(std::ifstream& stream);
29 NumericAddressedRegisterCatalogue catalogue;
30 MetadataCatalogue metadata;
35 JsonMapFileParser::JsonMapFileParser(std::string fileName) : _theImp(std::make_unique<Imp>(std::move(fileName))) {}
37 JsonMapFileParser::~JsonMapFileParser() =
default;
41 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> JsonMapFileParser::parse(std::ifstream& stream) {
42 return _theImp->parse(stream);
55 NLOHMANN_JSON_SERIALIZE_ENUM(
56 Access, {{Access::READ_ONLY,
"RO"}, {Access::READ_WRITE,
"RW"}, {Access::WRITE_ONLY,
"WO"}})
61 enum RepresentationType {
68 NLOHMANN_JSON_SERIALIZE_ENUM(RepresentationType,
69 {{RepresentationType::FIXED_POINT,
"fixedPoint"}, {RepresentationType::IEEE754,
"IEEE754"},
70 {RepresentationType::VOID,
"void"}, {RepresentationType::ASCII,
"string"}})
75 enum AddressType { IO, DMA, addressTypeNotSet };
76 NLOHMANN_JSON_SERIALIZE_ENUM(AddressType, {{AddressType::IO,
"IO"}, {AddressType::DMA,
"DMA"}})
85 friend void from_json(
const json& j, HexValue& hv) {
87 auto sdata = std::string(j);
89 hv.v = std::stoll(sdata,
nullptr, 0);
91 catch(std::invalid_argument& e) {
92 throw json::type_error::create(0,
"Cannot parse string '" + sdata +
"' as number.", &j);
94 catch(std::out_of_range& e) {
95 throw json::type_error::create(0,
"Number '" + sdata +
"' out of range.", &j);
104 friend void to_json(
json& j,
const HexValue& hv) { j = hv.v; }
112 struct JsonAddressSpaceEntry {
113 std::string engineeringUnit;
114 std::string description;
115 Access access{Access::accessNotSet};
116 std::vector<size_t> triggeredByInterrupt;
117 size_t numberOfElements{1};
118 size_t bytesPerElement{0};
120 struct DoubleBufferingInfo {
121 struct SecondAddress {
122 AddressType type{AddressType::DMA};
126 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(SecondAddress, type, channel, offset)
129 SecondAddress secondaryBufferAddress;
130 std::string enableRegister;
131 std::string readBufferRegister;
134 void fill(NumericAddressedRegisterInfo& info)
const {
135 info.doubleBuffer->address = secondaryBufferAddress.offset.v;
137 size_t secondaryBar =
138 secondaryBufferAddress.channel + (secondaryBufferAddress.type == AddressType::DMA ? 13 : 0);
139 if(secondaryBar != info.bar) {
141 ": double-buffered registers whose two buffers lie on different BARs are not supported (primary "
145 info.doubleBuffer->enableRegisterPath = enableRegister;
146 info.doubleBuffer->inactiveBufferRegisterPath = readBufferRegister;
147 info.doubleBuffer->index = index;
150 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
151 DoubleBufferingInfo, secondaryBufferAddress, enableRegister, readBufferRegister, index)
153 std::optional<DoubleBufferingInfo> doubleBuffering;
156 AddressType type{AddressType::IO};
158 HexValue offset{std::numeric_limits<size_t>::max()};
160 void fill(NumericAddressedRegisterInfo& info)
const {
161 assert(type != AddressType::addressTypeNotSet);
162 info.address = offset.v;
163 info.bar = channel + (type == AddressType::DMA ? 13 : 0);
166 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Address, type, channel, offset)
167 } address{AddressType::addressTypeNotSet};
170 struct Representation {
171 RepresentationType type{RepresentationType::FIXED_POINT};
172 uint32_t width{type != RepresentationType::representationNotSet ? 32U : 0U};
173 int32_t fractionalBits{0};
174 bool isSigned{
false};
175 uint32_t bitShift{0};
177 void fill(NumericAddressedRegisterInfo& info,
size_t offset,
size_t bytesPerElem)
const {
178 if(type != RepresentationType::representationNotSet) {
180 type != RepresentationType::IEEE754 ? isSigned :
true,
182 info.channels.back().bitOffset += bitShift;
184 info.isBitRange =
true;
188 Representation().fill(info, offset, bytesPerElem);
192 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Representation, type, width, fractionalBits, isSigned, bitShift)
193 } representation{RepresentationType::representationNotSet};
201 friend void from_json(
const nlohmann::json& j, SelectedBy& s) {
202 if(!j.contains(
"register")) {
205 if(!j.contains(
"value")) {
208 j.at(
"register").get_to(s.regPath);
209 j.at(
"value").get_to(s.value);
213 friend void to_json(nlohmann::json& j,
const SelectedBy& s) {
214 j = nlohmann::json{{
"register", s.regPath}, {
"value", s.value}};
221 std::string engineeringUnit;
222 std::string description;
224 size_t bytesPerElement{4};
225 Representation representation;
226 std::optional<SelectedBy> selectedBy;
227 std::map<std::string, ChannelChild> children;
229 void fill(NumericAddressedRegisterInfo& info,
const std::optional<SelectedBy>& inheritedSelectedBy)
const {
230 representation.fill(info, offset, bytesPerElement);
231 if(selectedBy || inheritedSelectedBy) {
232 const auto& selected = selectedBy ? *selectedBy : *inheritedSelectedBy;
233 RegisterPath selReg(selected.regPath);
234 selReg.setAltSeparator(
".");
235 info.channels.back().selectedBy.emplace(selReg, selected.value);
239 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
240 Channel, engineeringUnit, description, offset, bytesPerElement, representation, selectedBy, children)
245 struct ChannelChild {
246 std::string engineeringUnit;
247 std::string description;
248 Representation representation;
250 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ChannelChild, engineeringUnit, description, representation)
256 [[nodiscard]] std::vector<std::pair<std::string, const Channel*>> channelsInOffsetOrder()
const {
257 std::vector<std::pair<std::string, const Channel*>> result;
258 result.reserve(channels.size());
259 for(
const auto& [channelName, channel] : channels) {
260 result.emplace_back(channelName, &channel);
262 std::ranges::stable_sort(
263 result, [](
const auto& a,
const auto& b) {
return a.second->offset < b.second->offset; });
268 std::map<std::string, Channel> channels;
273 std::optional<SelectedBy> selectedBy;
275 void fill(NumericAddressedRegisterInfo& info,
const std::string& name,
const RegisterPath& parentName,
276 bool addressSetByParent,
const std::optional<SelectedBy>& inheritedSelectedBy,
277 const std::string& selectedBySource)
const {
278 info.pathName = parentName / name;
279 info.pathName.setAltSeparator(
".");
281 if(triggeredByInterrupt.empty()) {
282 if(access != Access::accessNotSet) {
285 else if(!addressSetByParent) {
290 if(access != Access::accessNotSet) {
292 "Register " + info.pathName +
": 'access' and 'triggeredByInterrupt' are mutually exclusive.");
294 info.interruptId = triggeredByInterrupt;
298 if(representation.type != RepresentationType::VOID) {
299 if(address.type != AddressType::addressTypeNotSet) {
301 if(channels.empty()) {
302 auto bPerElem = (bytesPerElement != 0 ? bytesPerElement : 4);
303 info.elementPitchBits = bPerElem * 8;
304 info.nElements = numberOfElements;
305 representation.fill(info, 0, bPerElem);
306 applyRegisterSelectedBy(info, inheritedSelectedBy);
309 info.elementPitchBits = pitch * 8;
310 info.nElements = numberOfElements;
313 for(
const auto& [channelName, channel] : channelsInOffsetOrder()) {
315 channel->fill(info, inheritedSelectedBy);
319 else if(addressSetByParent) {
320 if(channels.empty()) {
321 if(representation.type == RepresentationType::representationNotSet) {
323 " which inherited the address from parent!");
326 representation.fill(info, 0, (bytesPerElement != 0 ? bytesPerElement : info.elementPitchBits / 8));
327 applyRegisterSelectedBy(info, inheritedSelectedBy);
334 throw ChimeraTK::logic_error(
"Address not set but representation given in register " + parentName / name);
339 if(address.type != AddressType::addressTypeNotSet) {
342 if(triggeredByInterrupt.empty()) {
344 "Void-typed register " + parentName / name +
" needs 'triggeredByInterrupt' entry.");
348 info.interruptId = triggeredByInterrupt;
350 info.channels.clear();
354 if(doubleBuffering) {
355 info.doubleBuffer.emplace();
356 doubleBuffering->fill(info);
359 info.doubleBuffer.reset();
362 info.description = description;
363 info.engineeringUnit = engineeringUnit;
370 for(
const auto& channel : info.channels) {
371 if(channel.selectedBy) {
373 ": 'selectedBy' may only be used on read-only registers (Access::READ_ONLY or Access::INTERRUPT). "
374 "The offending 'selectedBy' is declared at '" +
375 selectedBySource +
"'.");
383 void applyRegisterSelectedBy(
384 NumericAddressedRegisterInfo& info,
const std::optional<SelectedBy>& inheritedSelectedBy)
const {
385 if(selectedBy || inheritedSelectedBy) {
386 const auto& selected = selectedBy ? *selectedBy : *inheritedSelectedBy;
387 RegisterPath selReg(selected.regPath);
388 selReg.setAltSeparator(
".");
389 info.channels.back().selectedBy.emplace(selReg, selected.value);
397 static NumericAddressedRegisterInfo::ChannelInfo makeChannelInfo(
const Representation& rep,
size_t wordBits,
398 const std::optional<NumericAddressedRegisterInfo::SelectedBy>& selectedBy) {
400 rep.type != RepresentationType::IEEE754 ? rep.isSigned :
true, DataType(
"int" +
std::to_string(wordBits)),
408 static void addSliceWithBufferViews(NumericAddressedRegisterCatalogue& catalogue,
409 NumericAddressedRegisterInfo& slice,
const RegisterPath& slicePath) {
410 catalogue.addRegister(slice);
411 if(!slice.doubleBuffer.has_value()) {
414 NumericAddressedRegisterInfo sliceBuf0 = slice;
415 sliceBuf0.pathName = slicePath +
"/BUF0";
416 sliceBuf0.doubleBuffer.reset();
418 sliceBuf0.computeDataDescriptor();
419 catalogue.addRegister(sliceBuf0);
420 NumericAddressedRegisterInfo sliceBuf1 = slice;
421 sliceBuf1.pathName = slicePath +
"/BUF1";
422 sliceBuf1.doubleBuffer.reset();
423 sliceBuf1.address = slice.doubleBuffer->address;
425 sliceBuf1.computeDataDescriptor();
426 catalogue.addRegister(sliceBuf1);
429 std::map<std::string, JsonAddressSpaceEntry> children;
431 void addInfos(NumericAddressedRegisterCatalogue& catalogue,
const std::string& name,
const RegisterPath& parentName,
432 bool addressSetByParent,
const std::optional<SelectedBy>& inheritedSelectedBy = std::nullopt,
433 const std::string& inheritedSelectedBySource =
"")
const {
438 const auto& effectiveSelectedBy = selectedBy ? selectedBy : inheritedSelectedBy;
441 RegisterPath thisPath = parentName / name;
442 thisPath.setAltSeparator(
".");
443 const std::string effectiveSelectedBySource = selectedBy ? std::string(thisPath) : inheritedSelectedBySource;
444 if(address.type != AddressType::addressTypeNotSet) {
446 NumericAddressedRegisterInfo my;
448 fill(my, name, parentName, addressSetByParent, effectiveSelectedBy, effectiveSelectedBySource);
449 my.computeDataDescriptor();
450 catalogue.addRegister(my);
451 if(!channels.empty()) {
456 for(
const auto& [channelName, channel] : channelsInOffsetOrder()) {
457 RegisterPath slicePath = my.pathName / channelName;
458 slicePath.setAltSeparator(
".");
460 if(catalogue.hasRegister(slicePath)) {
462 "Channel slice '" + (slicePath) +
"' collides with an already existing register.");
464 const auto& rep = channel->representation;
466 std::optional<NumericAddressedRegisterInfo::SelectedBy> channelSelectedBy = std::nullopt;
467 if(channel->selectedBy || effectiveSelectedBy) {
468 const auto& sb = channel->selectedBy ? *channel->selectedBy : *effectiveSelectedBy;
469 auto selReg = RegisterPath(sb.regPath);
470 selReg.setAltSeparator(
".");
471 channelSelectedBy.emplace(selReg, sb.value);
474 auto wordBits = channel->bytesPerElement * 8;
482 NumericAddressedRegisterInfo slice(slicePath, my.bar, my.address + channel->offset, my.nElements,
483 my.elementPitchBits, {makeChannelInfo(rep, wordBits, channelSelectedBy)}, sliceAccessType,
484 my.interruptId, my.doubleBuffer);
488 if(slice.doubleBuffer.has_value()) {
489 slice.doubleBuffer->address += channel->offset;
491 slice.isBitRange = (rep.bitShift != 0);
492 slice.computeDataDescriptor();
493 slice.engineeringUnit = channel->engineeringUnit;
494 slice.description = channel->description;
495 addSliceWithBufferViews(catalogue, slice, slicePath);
500 for(
const auto& [childName, child] : channel->children) {
501 const auto& crep = child.representation;
504 if((crep.bitShift == 0 && crep.width == wordBits) || (crep.bitShift + crep.width > wordBits)) {
507 RegisterPath childPath = slicePath / childName;
508 childPath.setAltSeparator(
".");
510 if(catalogue.hasRegister(childPath)) {
512 "Child slice '" + (childPath) +
"' collides with an already existing register.");
514 NumericAddressedRegisterInfo childSlice = slice;
515 childSlice.pathName = childPath;
516 childSlice.channels = {makeChannelInfo(crep, wordBits, channelSelectedBy)};
517 childSlice.isBitRange =
true;
518 childSlice.engineeringUnit = child.engineeringUnit;
519 childSlice.description = child.description;
520 childSlice.computeDataDescriptor();
521 addSliceWithBufferViews(catalogue, childSlice, childPath);
525 if(doubleBuffering.has_value()) {
527 NumericAddressedRegisterInfo buf0Register = my;
528 buf0Register.pathName = my.pathName +
"/BUF0";
529 buf0Register.doubleBuffer.reset();
531 buf0Register.computeDataDescriptor();
532 catalogue.addRegister(buf0Register);
533 NumericAddressedRegisterInfo buf1Register = my;
534 buf1Register.pathName = my.pathName +
"/BUF1";
535 buf1Register.doubleBuffer.reset();
536 buf1Register.address = doubleBuffering->secondaryBufferAddress.offset.v;
538 buf1Register.computeDataDescriptor();
539 catalogue.addRegister(buf1Register);
542 else if(representation.type != RepresentationType::representationNotSet) {
544 auto my = catalogue.getBackendRegister(parentName);
546 fill(my, name, parentName, addressSetByParent, effectiveSelectedBy,
547 effectiveSelectedBySource);
548 my.computeDataDescriptor();
549 catalogue.addRegister(my);
552 for(
const auto& [childName, child] : children) {
553 child.addInfos(catalogue, childName, parentName / name,
554 addressSetByParent || (address.type != AddressType::addressTypeNotSet), effectiveSelectedBy,
555 effectiveSelectedBySource);
559 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(JsonAddressSpaceEntry, engineeringUnit, description, access,
560 triggeredByInterrupt, numberOfElements, bytesPerElement, pitch, address, representation, children, channels,
561 doubleBuffering, selectedBy)
566 struct InterruptHandlerEntry {
570 std::map<std::string, InterruptHandlerEntry> subhandler;
572 void fill(
const std::vector<size_t>& intId, MetadataCatalogue& metadata)
const {
576 metadata.addMetadata(
"!" + jsonIntId.dump(), raw.dump());
579 for(
const auto& [subIntId, handler] : subhandler) {
580 std::vector<size_t> qualifiedSubIntId = intId;
581 qualifiedSubIntId.push_back(std::stoll(subIntId));
582 handler.fill(qualifiedSubIntId, metadata);
587 friend void from_json(
const json& j, InterruptHandlerEntry& e) {
589 e.raw.erase(
"subhandler");
594 for(
auto it = e.raw.begin(); it != e.raw.end();) {
595 if(!it.key().empty() && it.key().front() ==
'_') {
596 it = e.raw.erase(it);
603 for(
auto& value : e.raw) {
604 if(!value.is_object()) {
607 if(!value.contains(
"version")) {
608 value[
"version"] = 1;
610 for(
auto it = value.begin(); it != value.end();) {
611 if(!it.key().empty() && it.key().front() ==
'_') {
612 it = value.erase(it);
619 if(j.contains(
"subhandler")) {
620 e.subhandler = j.at(
"subhandler").get<std::map<std::string, InterruptHandlerEntry>>();
628 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> JsonMapFileParser::Imp::parse(std::ifstream& stream) {
631 auto data = json::parse(stream);
634 constexpr uint32_t supportedMapFormatMajor = 1;
635 constexpr uint32_t supportedMapFormatMinor = 0;
641 std::string ver = data.at(
"mapFormatVersion").get<std::string>();
645 auto [p, e1] = std::from_chars(ver.data(), ver.data() + ver.size(), major);
646 if(e1 != std::errc{} || p == ver.data() + ver.size() || *p++ !=
'.') {
650 auto [q, e2] = std::from_chars(p, ver.data() + ver.size(), minor);
651 if(e2 != std::errc{} || q != ver.data() + ver.size()) {
655 if(major != supportedMapFormatMajor || minor != supportedMapFormatMinor) {
660 std::map<std::string, JsonAddressSpaceEntry> addressSpace = data.at(
"addressSpace");
661 for(
const auto& [addressSpaceName, entry] : addressSpace) {
662 entry.addInfos(catalogue, addressSpaceName,
"/",
false);
668 std::set<std::pair<uint64_t, uint64_t>> addressesWithBitRange;
669 for(
auto& reg : catalogue) {
671 addressesWithBitRange.insert({reg.bar, reg.address});
674 if(addressesWithBitRange.size()) {
679 auto elementDataWidth = [](
const NumericAddressedRegisterInfo::ChannelInfo& c) {
682 return c.getRawType().getNumberOfBytes() * 8;
684 for(
auto& reg : catalogue) {
685 if((reg.channels.size() == 1) && (reg.channels[0].bitOffset == 0) &&
686 (reg.channels[0].width < elementDataWidth(reg.channels[0])) &&
687 (reg.elementPitchBits == elementDataWidth(reg.channels[0]))) {
688 if(addressesWithBitRange.find({reg.bar, reg.address}) != addressesWithBitRange.end()) {
689 reg.isBitRange =
true;
698 for(
const auto& reg : catalogue) {
699 for(
const auto& channel : reg.channels) {
700 if(!channel.selectedBy) {
703 std::string regPath = channel.selectedBy->regPath;
704 std::string channelName = reg.getRegisterName();
706 if(!catalogue.hasRegister(channel.selectedBy->regPath)) {
708 "' is gated by 'selectedBy' referencing undefined register '" + regPath +
"'.");
712 auto selectorReg = catalogue.getBackendRegister(channel.selectedBy->regPath);
713 for(
const auto& selectorChannel : selectorReg.channels) {
714 if(selectorChannel.selectedBy) {
716 regPath +
"' of channel '" + channelName +
717 "' is itself conditionally enabled by 'selectedBy'; a selector register must not be gated.");
723 for(
const auto& entry : data.at(
"metadata").items()) {
724 if(entry.key().empty()) {
726 "Error parsing JSON map file '" + fileName +
"': Metadata key must not be empty.");
728 if(entry.key()[0] ==
'_') {
731 metadata.addMetadata(entry.key(), entry.value());
735 InterruptHandlerEntry interruptHandler;
736 interruptHandler.subhandler = data.at(
"interruptHandler");
737 interruptHandler.fill({}, metadata);
739 return {std::move(catalogue), std::move(metadata)};
744 catch(
const json::exception& e) {
Access
Enum describing the access mode of the register:
Type
Enum descibing the data interpretation:
Exception thrown when a logic error has occured.
const char * what() const noexcept override
Return the message describing what exactly went wrong.
std::string to_string(const std::string &v)