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