ChimeraTK-DeviceAccess 03.25.00
Loading...
Searching...
No Matches
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 // extract mandatory address information
204 is >> std::setbase(0) >> pl.nElements >> std::setbase(0) >> pl.address >> std::setbase(0) >> pl.nBytes;
205 if(!is) {
206 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " + std::to_string(_lineNo));
207 }
208
209 // Note: default values for optional information are set in ParsedLine declaration
210 // extract bar
211 is >> std::setbase(0) >> pl.bar;
212
213 // extract width
214 if(!is.fail()) {
215 is >> std::setbase(0) >> pl.width;
216 if((pl.nElements > 0) && (pl.width > pl.nBytes / pl.nElements * 8)) {
217 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " +
218 std::to_string(_lineNo) + ": register width too big");
219 }
220 }
221
222 // extract bit interpretation field (nb. of fractional bits, IEEE754, VOID, ...)
223 if(!is.fail()) {
224 std::string bitInterpretation;
225 is >> bitInterpretation;
226 if(!is.fail()) {
227 // width is needed to determine whether type is VOID
228 std::tie(pl.type, pl.nFractionalBits) = getTypeAndNFractionalBits(bitInterpretation, pl.width);
229 if(pl.nFractionalBits > 1023 || pl.nFractionalBits < -1024) {
230 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " +
231 std::to_string(_lineNo) + ": too many fractional bits");
232 }
233 }
234 }
235
236 // extract signed flag
237 if(!is.fail()) {
238 is >> std::setbase(0) >> pl.signedFlag;
239 }
240
241 // extract access mode string (RO, RW, WO, INTERRUPT)
242 if(!is.fail()) {
243 std::string accessString;
244 is >> accessString;
245 if(!is.fail()) {
246 // first transform to uppercase
247 std::transform(accessString.begin(), accessString.end(), accessString.begin(),
248 [](unsigned char c) { return std::toupper(c); });
249
250 // first check if access mode is INTERRUPT
251 auto interruptId = getInterruptId(accessString);
252
253 if(!interruptId.empty()) {
254 pl.registerAccess = NumericAddressedRegisterInfo::Access::INTERRUPT;
255 pl.interruptID = interruptId;
256 }
257 else if(accessString == "RO") {
258 pl.registerAccess = NumericAddressedRegisterInfo::Access::READ_ONLY;
259 }
260 else if(accessString == "RW") {
261 pl.registerAccess = NumericAddressedRegisterInfo::Access::READ_WRITE;
262 }
263 else if(accessString == "WO") {
264 pl.registerAccess = NumericAddressedRegisterInfo::Access::WRITE_ONLY;
265 }
266 else {
267 throw ChimeraTK::logic_error("Parsing error in map file '" + _fileName + "' on line " +
268 std::to_string(_lineNo) + ": invalid data access");
269 }
270 }
271 }
272
273 checkFileConsitencyAndThrowIfError(pl.registerAccess, pl.type, pl.nElements, pl.address, pl.nBytes, pl.bar,
274 pl.width, pl.nFractionalBits, pl.signedFlag);
275
276 return pl;
277 }
278
279 /********************************************************************************************************************/
280
281 bool TraditionalMapFileParser::isScalarOr1D(const RegisterPath& pathName) {
282 auto [module, name] = splitStringAtLastDot(pathName);
283 return !boost::algorithm::starts_with(name, MULTIPLEXED_SEQUENCE_PREFIX) &&
284 !boost::algorithm::starts_with(name, SEQUENCE_PREFIX) &&
285 !boost::algorithm::starts_with(name, MEM_MULTIPLEXED_PREFIX) && !is2DNewStyle(module);
286 }
287
288 /********************************************************************************************************************/
289
290 bool TraditionalMapFileParser::is2D(const RegisterPath& pathName) {
291 auto [module, name] = splitStringAtLastDot(pathName);
292 return boost::algorithm::starts_with(name, MULTIPLEXED_SEQUENCE_PREFIX);
293 }
294
295 /********************************************************************************************************************/
296
297 bool TraditionalMapFileParser::is2DNewStyle(RegisterPath pathName) {
298 // Intentional copy of the parameter
299 pathName.setAltSeparator(".");
300 auto components = pathName.getComponents();
301 if(components.size() != 2) return false;
302 return boost::algorithm::starts_with(components[1], MEM_MULTIPLEXED_PREFIX);
303 }
304
305 /********************************************************************************************************************/
306
307 RegisterPath TraditionalMapFileParser::makeSequenceName(const RegisterPath& pathName, size_t index) {
308 auto [module, name] = splitStringAtLastDot(pathName);
309 assert(boost::algorithm::starts_with(name, MULTIPLEXED_SEQUENCE_PREFIX));
310 name = name.substr(strlen(MULTIPLEXED_SEQUENCE_PREFIX)); // strip prefix
311 auto r = RegisterPath(module) / (SEQUENCE_PREFIX + name + "_" + std::to_string(index));
312 r.setAltSeparator(".");
313 return r;
314 }
315
316 /********************************************************************************************************************/
317
318 RegisterPath TraditionalMapFileParser::make2DName(const RegisterPath& pathName, const std::string& prefix) {
319 auto [module, name] = splitStringAtLastDot(pathName);
320 assert(boost::algorithm::starts_with(name, prefix));
321 name = name.substr(prefix.size()); // strip prefix
322 auto r = RegisterPath(module) / name;
323 r.setAltSeparator(".");
324 return r;
325 }
326
327 /********************************************************************************************************************/
328
329 void TraditionalMapFileParser::handle2DNewStyle(const ParsedLine& pl) {
330 // search for sequence entries matching the given register, create ChannelInfos from them
331
332 // Find all channels associated with the area
333 std::list<ParsedLine> channelLines;
334 for(auto& [key, value] : _parsedLinesMap) {
335 if(key.startsWith(pl.pathName) and pl.pathName.length() < key.length()) {
336 // First sanity check, address must not be smaller than start address
337 if(value.address < pl.address) {
339 "Start address of channel smaller than 2D register start address ('" + pl.pathName + "').");
340 }
341 channelLines.push_back(value);
342 }
343 }
344
345 channelLines.sort([](auto& a, auto& b) { return a.address < b.address; });
346 make2DRegisterInfos(pl, channelLines, MEM_MULTIPLEXED_PREFIX);
347 }
348
349 void TraditionalMapFileParser::make2DRegisterInfos(
350 const ParsedLine& pl, std::list<ParsedLine>& channelLines, const std::string& prefix) {
351 if(channelLines.empty()) {
352 throw ChimeraTK::logic_error("No sequences found for register " + pl.pathName);
353 }
354 std::vector<NumericAddressedRegisterInfo::ChannelInfo> channels;
355 size_t bytesPerBlock = 0;
356
357 for(auto& channel : channelLines) {
358 channels.emplace_back(NumericAddressedRegisterInfo::ChannelInfo{uint32_t(channel.address - pl.address) * 8,
359 channel.type, channel.width, channel.nFractionalBits, channel.signedFlag});
360 bytesPerBlock += channel.nBytes;
361
362 if(channel.nBytes != 1 && channel.nBytes != 2 && channel.nBytes != 4 && channel.nBytes != 8) {
363 throw ChimeraTK::logic_error("Sequence word size must correspond to a primitive type");
364 }
365 }
366
367 assert(bytesPerBlock > 0);
368
369 // make sure channel bit interpretation widths are not wider than the actual channel width
370 for(size_t i = 0; i < channels.size() - 1; ++i) {
371 auto actualWidth = channels[i + 1].bitOffset - channels[i].bitOffset;
372 if(channels[i].width > actualWidth) {
373 channels[i].width = actualWidth;
374 }
375 }
376 {
377 // last channel needs special treatment
378 auto actualWidth = bytesPerBlock * 8 - channels.back().bitOffset;
379 if(channels.back().width > actualWidth) {
380 channels.back().width = actualWidth;
381 }
382 }
383
384 // compute number of blocks (= samples per channel)
385 auto nBlocks = static_cast<uint32_t>(std::floor(pl.nBytes / bytesPerBlock));
386 auto name2D = make2DName(pl.pathName, prefix);
387 auto registerInfo = NumericAddressedRegisterInfo(
388 name2D, pl.bar, pl.address, nBlocks, bytesPerBlock * 8, channels, pl.registerAccess, pl.interruptID);
389 _pmap.addRegister(registerInfo);
390
391 // create 1D entry for reading the multiplexed raw data
392 if(!pl.nBytes || !pl.nElements) {
393 throw ChimeraTK::logic_error(pl.pathName + ": nBytes and nElements cannot be zero");
394 }
395 assert(pl.nBytes % 4 == 0);
396 auto regSize = 4;
397 auto width = 32;
398 if(pl.nBytes / pl.nElements == 8) {
399 regSize = 8;
400 width = 64;
401 }
402 auto registerInfoMuxedRaw =
403 NumericAddressedRegisterInfo(name2D + ".MULTIPLEXED_RAW", pl.nBytes / regSize, pl.address, pl.nBytes, pl.bar,
404 width, 0, true, pl.registerAccess, NumericAddressedRegisterInfo::Type::FIXED_POINT, pl.interruptID);
405 _pmap.addRegister(registerInfoMuxedRaw);
406 }
407
408 /********************************************************************************************************************/
409
410 void TraditionalMapFileParser::handle2D(const ParsedLine& pl) {
411 // search for sequence entries matching the given register, create ChannelInfos from them
412 std::list<ParsedLine> channelLines;
413 while(true) {
414 auto it = _parsedLinesMap.find(makeSequenceName(pl.pathName, channelLines.size()));
415 if(it == _parsedLinesMap.end()) break;
416 if(it->second.address < pl.address) {
418 "Start address of channel smaller than 2D register start address ('" + pl.pathName + "').");
419 }
420 channelLines.push_back(it->second);
421 }
422
423 make2DRegisterInfos(pl, channelLines, MULTIPLEXED_SEQUENCE_PREFIX);
424 }
425
426 /********************************************************************************************************************/
427
428} // 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)