ChimeraTK-DeviceAccess 03.20.00
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
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 <nlohmann/json.hpp>
7
8#include <boost/algorithm/string.hpp>
9
10#include <string>
11
12using json = nlohmann::json;
13
14namespace ChimeraTK::detail {
15
16 /********************************************************************************************************************/
17
18 struct JsonAddressSpaceEntry;
19
20 struct JsonMapFileParser::Imp {
21 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> parse(std::ifstream& stream);
22
23 std::string fileName;
24 NumericAddressedRegisterCatalogue catalogue;
25 MetadataCatalogue metadata;
26 };
27
28 /********************************************************************************************************************/
29
30 JsonMapFileParser::JsonMapFileParser(std::string fileName) : _theImp(std::make_unique<Imp>(std::move(fileName))) {}
31
32 JsonMapFileParser::~JsonMapFileParser() = default;
33
34 /********************************************************************************************************************/
35
36 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> JsonMapFileParser::parse(std::ifstream& stream) {
37 return _theImp->parse(stream);
38 }
39
40 /********************************************************************************************************************/
41 /********************************************************************************************************************/
42
43 // map Access enum to JSON as strings. Need to redefine the strongly typed enums as old-fashioned ones....
44 enum Access {
48 accessNotSet
49 };
50 NLOHMANN_JSON_SERIALIZE_ENUM(
51 Access, {{Access::READ_ONLY, "RO"}, {Access::READ_WRITE, "RW"}, {Access::WRITE_ONLY, "WO"}})
52
53 /********************************************************************************************************************/
54
55 // map RepresentationType enum to JSON as strings
56 enum RepresentationType {
61 representationNotSet
62 };
63 NLOHMANN_JSON_SERIALIZE_ENUM(RepresentationType,
64 {{RepresentationType::FIXED_POINT, "fixedPoint"}, {RepresentationType::IEEE754, "IEEE754"},
65 {RepresentationType::VOID, "void"}, {RepresentationType::ASCII, "string"}})
66
67 /********************************************************************************************************************/
68
69 // map AddressType enum to JSON as strings
70 enum AddressType { IO, DMA, addressTypeNotSet };
71 NLOHMANN_JSON_SERIALIZE_ENUM(AddressType, {{AddressType::IO, "IO"}, {AddressType::DMA, "DMA"}})
72
73 /********************************************************************************************************************/
74
75 // Allow hex string representation of values (but still accept plain int as well)
76 struct HexValue {
77 size_t v;
78
79 // NOLINTNEXTLINE(readability-identifier-naming)
80 friend void from_json(const json& j, HexValue& hv) {
81 if(j.is_string()) {
82 auto sdata = std::string(j);
83 try {
84 hv.v = std::stoll(sdata, nullptr, 0);
85 }
86 catch(std::invalid_argument& e) {
87 throw json::type_error::create(0, "Cannot parse string '" + sdata + "' as number.", &j);
88 }
89 catch(std::out_of_range& e) {
90 throw json::type_error::create(0, "Number '" + sdata + "' out of range.", &j);
91 }
92 }
93 else {
94 hv.v = j;
95 }
96 }
97
98 // NOLINTNEXTLINE(readability-identifier-naming)
99 friend void to_json(json& j, const HexValue& hv) { j = hv.v; }
100 };
101
102 /********************************************************************************************************************/
103 /********************************************************************************************************************/
104
107 struct JsonAddressSpaceEntry {
108 std::string name;
109 std::string engineeringUnit;
110 std::string description;
111 Access access{Access::accessNotSet};
112 std::vector<size_t> triggeredByInterrupt;
113 size_t numberOfElements{1};
114 size_t bytesPerElement{0};
115
116 struct Address {
117 AddressType type{AddressType::IO};
118 size_t channel{0};
119 HexValue offset{std::numeric_limits<size_t>::max()};
120
121 void fill(NumericAddressedRegisterInfo& info) const {
122 assert(type != AddressType::addressTypeNotSet);
123 info.address = offset.v;
124 info.bar = channel + (type == AddressType::DMA ? 13 : 0);
125 }
126
127 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Address, type, channel, offset)
128 } address{AddressType::addressTypeNotSet};
129
130 struct Representation {
131 RepresentationType type{RepresentationType::FIXED_POINT};
132 uint32_t width{type != RepresentationType::representationNotSet ? 32U : 0U};
133 int32_t fractionalBits{0};
134 bool isSigned{false};
135
136 void fill(NumericAddressedRegisterInfo& info, size_t offset) const {
137 if(type != RepresentationType::representationNotSet) {
138 info.channels.emplace_back(8 * offset, NumericAddressedRegisterInfo::Type(type), width, fractionalBits,
139 type != RepresentationType::IEEE754 ? isSigned : true);
140 }
141 else {
142 Representation().fill(info, offset);
143 }
144 }
145
146 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Representation, type, width, fractionalBits, isSigned)
147 } representation{RepresentationType::representationNotSet};
148
149 struct ChannelTab {
150 size_t numberOfElements{0};
151 size_t pitch{0};
152
153 struct Channel {
154 std::string name;
155 std::string engineeringUnit;
156 std::string description;
157 size_t offset;
158 size_t bytesPerElement{4};
159 Representation representation{};
160
161 void fill(NumericAddressedRegisterInfo& info) const { representation.fill(info, offset); }
162
163 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
164 Channel, name, engineeringUnit, description, offset, bytesPerElement, representation)
165 };
166
167 std::vector<Channel> channels;
168
169 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(ChannelTab, numberOfElements, pitch, channels)
170 };
171 std::vector<ChannelTab> channelTabs;
172
173 void fill(NumericAddressedRegisterInfo& info, const RegisterPath& parentName) const {
174 info.pathName = parentName / name;
175
176 if(triggeredByInterrupt.empty()) {
177 info.registerAccess = access != Access::accessNotSet ? NumericAddressedRegisterInfo::Access(access) :
179 }
180 else {
181 if(access != Access::accessNotSet) {
183 "Register " + info.pathName + ": 'access' and 'triggeredByInterrupt' are mutually exclusive.");
184 }
185 info.interruptId = triggeredByInterrupt;
187 }
188
189 if(address.type != AddressType::addressTypeNotSet) {
190 if(representation.type == RepresentationType::VOID) {
191 throw ChimeraTK::logic_error("Address is set for void-typed register " + info.pathName);
192 }
193 address.fill(info);
194 if(channelTabs.empty()) {
195 info.elementPitchBits = bytesPerElement * 8;
196 info.nElements = numberOfElements;
197 representation.fill(info, 0);
198 }
199 else {
200 if(channelTabs[0].channels.empty()) {
201 throw ChimeraTK::logic_error("Empty channel definition in register " + info.pathName);
202 }
203 info.elementPitchBits = channelTabs[0].pitch * 8;
204 info.nElements = channelTabs[0].numberOfElements;
205 for(const auto& channel : channelTabs[0].channels) {
206 channel.fill(info);
207 }
208 }
209 }
210 else {
211 if(representation.type != RepresentationType::VOID) {
212 throw ChimeraTK::logic_error("Address not set but representation given in register " + parentName / name);
213 }
214 if(triggeredByInterrupt.empty()) {
216 "Void-typed register " + parentName / name + " needs 'triggeredByInterrupt' entry.");
217 }
218 info.nElements = 0;
219 info.dataDescriptor = DataDescriptor{DataDescriptor::FundamentalType::nodata};
220 info.interruptId = triggeredByInterrupt;
221 info.channels.emplace_back(0, NumericAddressedRegisterInfo::Type::VOID, 0, 0, false);
222 }
223 }
224
225 std::vector<JsonAddressSpaceEntry> children;
226
227 void addInfos(NumericAddressedRegisterCatalogue& catalogue, const RegisterPath& parentName) const {
228 if(name.empty()) {
229 throw ChimeraTK::logic_error("Entry in module " + parentName + " has no name.");
230 }
231 if(address.type != AddressType::addressTypeNotSet ||
232 representation.type != RepresentationType::representationNotSet) {
233 NumericAddressedRegisterInfo my;
234 my.channels.clear(); // default constructor already creates a channel with default settings...
235 fill(my, parentName);
236 my.computeDataDescriptor();
237 catalogue.addRegister(my);
238 }
239 for(const auto& child : children) {
240 child.addInfos(catalogue, parentName / name);
241 }
242 }
243
244 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(JsonAddressSpaceEntry, name, engineeringUnit, description, access,
245 triggeredByInterrupt, numberOfElements, bytesPerElement, address, representation, children, channelTabs)
246 };
247
248 /********************************************************************************************************************/
249
250 struct InterruptHandlerEntry {
251 struct Controller {
252 std::string path;
253 std::set<std::string> options;
254 int version{1};
255 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Controller, path, options, version)
256 } INTC;
257
258 std::map<std::string, InterruptHandlerEntry> subhandler;
259
260 void fill(const std::vector<size_t>& intId, MetadataCatalogue& metadata) const {
261 if(!intId.empty()) {
262 json jsonIntId;
263 jsonIntId = intId;
264 json jsonController;
265 jsonController = INTC;
266 metadata.addMetadata("!" + jsonIntId.dump(), R"({"INTC":)" + jsonController.dump() + "}");
267 }
268
269 for(const auto& [subIntId, handler] : subhandler) {
270 std::vector<size_t> qualfiedSubIntId = intId;
271 qualfiedSubIntId.push_back(std::stoll(subIntId));
272 handler.fill(qualfiedSubIntId, metadata);
273 }
274 }
275
276 NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(InterruptHandlerEntry, INTC, subhandler)
277 };
278
279 /********************************************************************************************************************/
280 /********************************************************************************************************************/
281
282 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> JsonMapFileParser::Imp::parse(std::ifstream& stream) {
283 // read and parse JSON data
284 try {
285 auto data = json::parse(stream);
286
287 std::vector<JsonAddressSpaceEntry> addressSpace = data.at("addressSpace");
288 for(const auto& entry : addressSpace) {
289 entry.addInfos(catalogue, "/");
290 }
291
292 for(const auto& entry : data.at("metadata").items()) {
293 if(entry.key().empty()) {
295 "Error parsing JSON map file '" + fileName + "': Metadata key must not be empty.");
296 }
297 if(entry.key()[0] == '_') {
298 continue;
299 }
300 metadata.addMetadata(entry.key(), entry.value());
301 }
302
303 // backwards compatibility: interrupt handler description is expected to be in metadata
304 InterruptHandlerEntry interruptHandler;
305 interruptHandler.subhandler = data.at("interruptHandler");
306 interruptHandler.fill({}, metadata);
307
308 return {std::move(catalogue), std::move(metadata)};
309 }
310 catch(const ChimeraTK::logic_error& e) {
311 throw ChimeraTK::logic_error("Error parsing JSON map file '" + fileName + "': " + e.what());
312 }
313 catch(const json::exception& e) {
314 throw ChimeraTK::logic_error("Error parsing JSON map file '" + fileName + "': " + e.what());
315 }
316 }
317
318 /********************************************************************************************************************/
319
320} // 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