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