ChimeraTK-DeviceAccess 03.20.00
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
TraditionalMapFileParser.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
5
6#include "NumericAddressedBackendMuxedRegisterAccessor.h" // for the MULTIPLEXED_SEQUENCE_PREFIX constant
7
8#include <boost/algorithm/string.hpp>
9
10#include <algorithm>
11#include <iostream>
12#include <sstream>
13#include <string>
14
15namespace ChimeraTK::detail {
16
17 /********************************************************************************************************************/
18
19 TraditionalMapFileParser::TraditionalMapFileParser(std::string fileName) : _fileName(std::move(fileName)) {}
20
21 /********************************************************************************************************************/
22
23 std::pair<NumericAddressedRegisterCatalogue, MetadataCatalogue> TraditionalMapFileParser::parse(
24 std::ifstream& stream) {
25 std::string line;
26 while(std::getline(stream, line)) {
27 _lineNo++;
28
29 // Remove whitespace from beginning of line
30 line.erase(line.begin(), std::find_if(line.begin(), line.end(), [](int c) { return !isspace(c); }));
31
32 // Remove comments from the end of the line
33 auto pos = line.find('#');
34 if(pos != std::string::npos) {
35 line.erase(pos, std::string::npos);
36 }
37
38 // Ignore empty lines (including all-comment lines)
39 if(line.empty()) {
40 continue;
41 }
42
43 // Parse meta data line
44 if(line[0] == '@') {
45 parseMetaData(line);
46 continue;
47 }
48
49 // Parse register line
50 _parsedLines.push_back(parseLine(line));
51 }
52
53 // create map of register names to parsed lines
54 // This cannot be done in the above parsing loop, as the vector might get resized which invalidates the references
55 for(const auto& pl : _parsedLines) {
56 _parsedLinesMap.emplace(pl.pathName, pl);
57 }
58
59 // add registers to the catalogue
60 for(const auto& pl : _parsedLines) {
61 if(isScalarOr1D(pl.pathName)) {
62 auto registerInfo = NumericAddressedRegisterInfo(pl.pathName, pl.nElements, pl.address, pl.nBytes, pl.bar,
63 pl.width, pl.nFractionalBits, pl.signedFlag, pl.registerAccess, pl.type, pl.interruptID);
64 _pmap.addRegister(registerInfo);
65 }
66 else if(is2D(pl.pathName)) {
67 handle2D(pl);
68 }
69 else if(is2DNewStyle(pl.pathName)) {
70 handle2DNewStyle(pl);
71 }
72 }
73
74 return {std::move(_pmap), std::move(_metadataCatalogue)};
75 }
76
77 /********************************************************************************************************************/
78
79 std::pair<RegisterPath, std::string> TraditionalMapFileParser::splitStringAtLastDot(RegisterPath moduleDotName) {
80 moduleDotName.setAltSeparator(".");
81 auto regname = moduleDotName.getComponents().back();
82 moduleDotName--;
83 return {moduleDotName, regname};
84 }
85
86 /********************************************************************************************************************/
87
88 std::pair<NumericAddressedRegisterInfo::Type, int> TraditionalMapFileParser::getTypeAndNFractionalBits(
89 const std::string& bitInterpretation, unsigned int width) {
90 if(width == 0) return {NumericAddressedRegisterInfo::Type::VOID, 0};
91 if(bitInterpretation == "IEEE754") return {NumericAddressedRegisterInfo::Type::IEEE754, 0};
92 if(bitInterpretation == "ASCII") return {NumericAddressedRegisterInfo::Type::ASCII, 0};
93
94 // If it is a digit the implicit interpretation is FixedPoint
95 try {
96 int nBits = std::stoi(bitInterpretation, nullptr,
97 0); // base 0 = auto, hex or dec or oct
98 return {NumericAddressedRegisterInfo::Type::FIXED_POINT, nBits};
99 }
100 catch(std::exception& e) {
101 throw ChimeraTK::logic_error(std::string("Map file error in bitInterpretation: wrong argument '") +
102 bitInterpretation + "', caught exception: " + e.what());
103 }
104 }
105
106 /********************************************************************************************************************/
107 // FIXME: This funtion does a lot of string copying. This can be optimised, for instance by using a string_view.
108 std::vector<size_t> TraditionalMapFileParser::getInterruptId(std::string accessTypeStr) {
109 std::string strToFind("INTERRUPT");
110 auto pos = accessTypeStr.find(strToFind);
111 if(pos == std::string::npos) return {};
112 std::vector<size_t> retVal;
113
114 accessTypeStr.erase(pos, strToFind.length());
115
116 size_t delimiterPos;
117 do {
118 delimiterPos = accessTypeStr.find(':');
119 std::string interruptStr = accessTypeStr.substr(0, delimiterPos);
120 size_t interruptNumber = 0;
121 try {
122 interruptNumber = std::stoul(interruptStr, nullptr, 0); // base 0 = auto, hex or dec or oct
123 }
124 catch(std::exception& e) {
126 std::string("Map file error in accessString: wrong argument in interrupt controller number. Argument: '") +
127 interruptStr + "', caught exception: " + e.what());
128 }
129 retVal.push_back(interruptNumber);
130
131 // cut off the already processed part and process the rest
132 if(delimiterPos != std::string::npos) {
133 accessTypeStr = accessTypeStr.substr(delimiterPos + 1);
134 }
135 } while(delimiterPos != std::string::npos);
136
137 return retVal;
138 }
139
140 /********************************************************************************************************************/
141
142 void TraditionalMapFileParser::checkFileConsitencyAndThrowIfError(
143 NumericAddressedRegisterInfo::Access registerAccessMode, NumericAddressedRegisterInfo::Type registerType,
144 uint32_t nElements, uint64_t address, uint32_t nBytes, uint64_t bar, uint32_t width, int32_t nFractionalBits,
145 bool signedFlag) {
146 //
147 // if type is VOID, access mode cannot me read only
148 if(registerType == NumericAddressedRegisterInfo::Type::VOID &&
149 registerAccessMode == NumericAddressedRegisterInfo::Access::READ_ONLY) {
150 throw ChimeraTK::logic_error(std::string("Map file error. Register Type is VOID and access mode is READ only. "));
151 }
152 //
153 // if register type is VOID and push-type. then all fields must be '0'
154 if(registerType == NumericAddressedRegisterInfo::Type::VOID &&
155 registerAccessMode == NumericAddressedRegisterInfo::Access::INTERRUPT) {
156 if(width || nElements || address || nBytes || bar || nFractionalBits || signedFlag) {
158 std::string("Map file error. Register Type is VOID (width field set to 0). All other fields must be '0'."));
159 }
160 }
161 }
162
163 /********************************************************************************************************************/
164
165 void TraditionalMapFileParser::parseMetaData(std::string line) {
166 std::string metadata_name, metadata_value;
167
168 // Remove the '@' character...
169 line.erase(line.begin(), line.begin() + 1);
170
171 // ... and remove all the whitespace after it
172 line.erase(line.begin(), std::find_if(line.begin(), line.end(), [](int c) { return !isspace(c); }));
173
174 std::istringstream is;
175 is.str(line);
176 is >> metadata_name;
177 if(!is) {
178 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " + std::to_string(_lineNo));
179 }
180 // remove name from the string
181 line.erase(line.begin(), line.begin() + static_cast<std::string::difference_type>(metadata_name.length()));
182
183 line.erase(std::remove_if(line.begin(), line.end(), [](unsigned char x) { return std::isspace(x); }),
184 line.end()); // remove whitespaces from rest of the string (before and after the value)
185 metadata_value = line;
186 _metadataCatalogue.addMetadata(metadata_name, metadata_value);
187 is.clear();
188 }
189
190 /********************************************************************************************************************/
191
192 TraditionalMapFileParser::ParsedLine TraditionalMapFileParser::parseLine(const std::string& line) {
193 ParsedLine pl;
194
195 std::istringstream is;
196 is.str(line);
197
198 // extract register name
199 std::string name;
200 is >> name;
201 pl.pathName = name;
202 pl.pathName.setAltSeparator(".");
203
204 // extract mandatory address information
205 is >> std::setbase(0) >> pl.nElements >> std::setbase(0) >> pl.address >> std::setbase(0) >> pl.nBytes;
206 if(!is) {
207 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " + std::to_string(_lineNo));
208 }
209
210 // Note: default values for optional information are set in ParsedLine declaration
211
212 // extract bar
213 is >> std::setbase(0) >> pl.bar;
214
215 // extract width
216 if(!is.fail()) {
217 is >> std::setbase(0) >> pl.width;
218 if(pl.width > 32) {
219 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " +
220 std::to_string(_lineNo) + ": register width too big");
221 }
222 }
223
224 // extract bit interpretation field (nb. of fractional bits, IEEE754, VOID, ...)
225 if(!is.fail()) {
226 std::string bitInterpretation;
227 is >> bitInterpretation;
228 if(!is.fail()) {
229 // width is needed to determine whether type is VOID
230 std::tie(pl.type, pl.nFractionalBits) = getTypeAndNFractionalBits(bitInterpretation, pl.width);
231 if(pl.nFractionalBits > 1023 || pl.nFractionalBits < -1024) {
232 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " +
233 std::to_string(_lineNo) + ": too many fractional bits");
234 }
235 }
236 }
237
238 // extract signed flag
239 if(!is.fail()) {
240 is >> std::setbase(0) >> pl.signedFlag;
241 }
242
243 // extract access mode string (RO, RW, WO, INTERRUPT)
244 if(!is.fail()) {
245 std::string accessString;
246 is >> accessString;
247 if(!is.fail()) {
248 // first transform to uppercase
249 std::transform(accessString.begin(), accessString.end(), accessString.begin(),
250 [](unsigned char c) { return std::toupper(c); });
251
252 // first check if access mode is INTERRUPT
253 auto interruptId = getInterruptId(accessString);
254
255 if(!interruptId.empty()) {
256 pl.registerAccess = NumericAddressedRegisterInfo::Access::INTERRUPT;
257 pl.interruptID = interruptId;
258 }
259 else if(accessString == "RO") {
260 pl.registerAccess = NumericAddressedRegisterInfo::Access::READ_ONLY;
261 }
262 else if(accessString == "RW") {
263 pl.registerAccess = NumericAddressedRegisterInfo::Access::READ_WRITE;
264 }
265 else if(accessString == "WO") {
266 pl.registerAccess = NumericAddressedRegisterInfo::Access::WRITE_ONLY;
267 }
268 else {
269 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " +
270 std::to_string(_lineNo) + ": invalid data access");
271 }
272 }
273 }
274
275 checkFileConsitencyAndThrowIfError(pl.registerAccess, pl.type, pl.nElements, pl.address, pl.nBytes, pl.bar,
276 pl.width, pl.nFractionalBits, pl.signedFlag);
277
278 return pl;
279 }
280
281 /********************************************************************************************************************/
282
283 bool TraditionalMapFileParser::isScalarOr1D(const RegisterPath& pathName) {
284 auto [module, name] = splitStringAtLastDot(pathName);
285 return !boost::algorithm::starts_with(name, MULTIPLEXED_SEQUENCE_PREFIX) &&
286 !boost::algorithm::starts_with(name, SEQUENCE_PREFIX) &&
287 !boost::algorithm::starts_with(name, MEM_MULTIPLEXED_PREFIX) && !is2DNewStyle(module);
288 }
289
290 /********************************************************************************************************************/
291
292 bool TraditionalMapFileParser::is2D(const RegisterPath& pathName) {
293 auto [module, name] = splitStringAtLastDot(pathName);
294 return boost::algorithm::starts_with(name, MULTIPLEXED_SEQUENCE_PREFIX);
295 }
296
297 /********************************************************************************************************************/
298
299 bool TraditionalMapFileParser::is2DNewStyle(RegisterPath pathName) {
300 // Intentional copy of the parameter
301 pathName.setAltSeparator(".");
302 auto components = pathName.getComponents();
303 if(components.size() != 2) return false;
304 return boost::algorithm::starts_with(components[1], MEM_MULTIPLEXED_PREFIX);
305 }
306
307 /********************************************************************************************************************/
308
309 RegisterPath TraditionalMapFileParser::makeSequenceName(const RegisterPath& pathName, size_t index) {
310 auto [module, name] = splitStringAtLastDot(pathName);
311 assert(boost::algorithm::starts_with(name, MULTIPLEXED_SEQUENCE_PREFIX));
312 name = name.substr(strlen(MULTIPLEXED_SEQUENCE_PREFIX)); // strip prefix
313 auto r = RegisterPath(module) / (SEQUENCE_PREFIX + name + "_" + std::to_string(index));
314 r.setAltSeparator(".");
315 return r;
316 }
317
318 /********************************************************************************************************************/
319
320 RegisterPath TraditionalMapFileParser::make2DName(const RegisterPath& pathName, const std::string& prefix) {
321 auto [module, name] = splitStringAtLastDot(pathName);
322 assert(boost::algorithm::starts_with(name, prefix));
323 name = name.substr(prefix.size()); // strip prefix
324 auto r = RegisterPath(module) / name;
325 r.setAltSeparator(".");
326 return r;
327 }
328
329 /********************************************************************************************************************/
330
331 void TraditionalMapFileParser::handle2DNewStyle(const ParsedLine& pl) {
332 // search for sequence entries matching the given register, create ChannelInfos from them
333
334 // Find all channels associated with the area
335 std::list<ParsedLine> channelLines;
336 for(auto& [key, value] : _parsedLinesMap) {
337 if(key.startsWith(pl.pathName) and pl.pathName.length() < key.length()) {
338 // First sanity check, address must not be smaller than start address
339 if(value.address < pl.address) {
341 "Start address of channel smaller than 2D register start address ('" + pl.pathName + "').");
342 }
343 channelLines.push_back(value);
344 }
345 }
346
347 channelLines.sort([](auto& a, auto& b) { return a.address < b.address; });
348 make2DRegisterInfos(pl, channelLines, MEM_MULTIPLEXED_PREFIX);
349 }
350
351 void TraditionalMapFileParser::make2DRegisterInfos(
352 const ParsedLine& pl, std::list<ParsedLine>& channelLines, const std::string& prefix) {
353 if(channelLines.empty()) {
354 throw ChimeraTK::logic_error("No sequences found for register " + pl.pathName);
355 }
356
357 std::vector<NumericAddressedRegisterInfo::ChannelInfo> channels;
358 size_t bytesPerBlock = 0;
359
360 for(auto& channel : channelLines) {
361 channels.emplace_back(NumericAddressedRegisterInfo::ChannelInfo{uint32_t(channel.address - pl.address) * 8,
362 channel.type, channel.width, channel.nFractionalBits, channel.signedFlag});
363 bytesPerBlock += channel.nBytes;
364 if(channel.nBytes != 1 && channel.nBytes != 2 && channel.nBytes != 4) {
365 throw ChimeraTK::logic_error("Sequence word size must correspond to a primitive type");
366 }
367 }
368
369 assert(bytesPerBlock > 0);
370
371 // make sure channel bit interpretation widths are not wider than the actual channel width
372 for(size_t i = 0; i < channels.size() - 1; ++i) {
373 auto actualWidth = channels[i + 1].bitOffset - channels[i].bitOffset;
374 if(channels[i].width > actualWidth) {
375 channels[i].width = actualWidth;
376 }
377 }
378 {
379 // last channel needs special treatment
380 auto actualWidth = bytesPerBlock * 8 - channels.back().bitOffset;
381 if(channels.back().width > actualWidth) {
382 channels.back().width = actualWidth;
383 }
384 }
385
386 // compute number of blocks (= samples per channel)
387 auto nBlocks = static_cast<uint32_t>(std::floor(pl.nBytes / bytesPerBlock));
388 auto name2D = make2DName(pl.pathName, prefix);
389 auto registerInfo = NumericAddressedRegisterInfo(
390 name2D, pl.bar, pl.address, nBlocks, bytesPerBlock * 8, channels, pl.registerAccess, pl.interruptID);
391 _pmap.addRegister(registerInfo);
392
393 // create 1D entry for reading the multiplexed raw data
394 assert(pl.nBytes % 4 == 0);
395 auto registerInfoMuxedRaw =
396 NumericAddressedRegisterInfo(name2D + ".MULTIPLEXED_RAW", pl.nBytes / 4, pl.address, pl.nBytes, pl.bar, 32, 0,
397 true, pl.registerAccess, NumericAddressedRegisterInfo::Type::FIXED_POINT, pl.interruptID);
398 _pmap.addRegister(registerInfoMuxedRaw);
399 }
400
401 /********************************************************************************************************************/
402
403 void TraditionalMapFileParser::handle2D(const ParsedLine& pl) {
404 // search for sequence entries matching the given register, create ChannelInfos from them
405 std::list<ParsedLine> channelLines;
406 while(true) {
407 auto it = _parsedLinesMap.find(makeSequenceName(pl.pathName, channelLines.size()));
408 if(it == _parsedLinesMap.end()) break;
409 if(it->second.address < pl.address) {
411 "Start address of channel smaller than 2D register start address ('" + pl.pathName + "').");
412 }
413 channelLines.push_back(it->second);
414 }
415
416 make2DRegisterInfos(pl, channelLines, MULTIPLEXED_SEQUENCE_PREFIX);
417 }
418
419 /********************************************************************************************************************/
420
421} // namespace ChimeraTK::detail
Exception thrown when a logic error has occured.
Definition Exception.h:51
STL namespace.
std::string to_string(const std::string &v)