ChimeraTK-DeviceAccess 03.31.00
Loading...
Searching...
No Matches
testJsonMapFileParser.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#define BOOST_TEST_DYN_LINK
5
6#define BOOST_TEST_MODULE JsonMapFileParser
7
8#include "Exception.h"
9#include "MapFileParser.h"
10
11using namespace ChimeraTK;
12
13#include <nlohmann/json.hpp>
14
15#include <boost/pointer_cast.hpp>
16#include <boost/test/unit_test.hpp>
17
18#include <unistd.h>
19
20#include <fstream>
21#include <string>
22#include <vector>
23using namespace boost::unit_test_framework;
24
25/**********************************************************************************************************************/
26/**********************************************************************************************************************/
27
28// Helper used by the selectedBy fault tests below: load simpleJsonFile.jmap as the base map, overwrite the
29// selectedBy of the existing COLLISION/FD/Sel0 channel with the supplied (possibly malformed) one, write the result
30// to <outFile>, then parse it. This replaces the standalone selectedBy*.jmap fixtures: the fault is injected into an
31// existing entry of the full simpleJsonFile map (via nlohmann-json) and the produced file is what gets parsed, so no
32// separate fault-only map files need to be kept.
33static std::pair<ChimeraTK::NumericAddressedRegisterCatalogue, ChimeraTK::MetadataCatalogue>
34 parseInjectedSelectedByFault(const std::string& outFile, const nlohmann::json& selectedBy) {
35 std::ifstream base("simpleJsonFile.jmap");
36 nlohmann::json map = nlohmann::json::parse(base);
37
38 // Overwrite the selectedBy of an existing channel (COLLISION/FD/Sel0) already carrying one, so the fault is
39 // injected into an existing entry rather than introducing a new register.
40 map["addressSpace"]["COLLISION"]["children"]["FD"]["channels"]["Sel0"]["selectedBy"] = selectedBy;
41
42 std::ofstream(outFile) << map.dump(2);
43 return ChimeraTK::MapFileParser::parse(outFile);
44}
45
46// Helper used by the map format version tests below: load simpleJsonFile.jmap as the base map, set the top-level
47// mapFormatVersion entry to the supplied value (or remove it when the value is null), write the result to <outFile>,
48// then parse it. As with the selectedBy fault helper above, this avoids keeping a set of near-identical variant
49// fixtures: the version under test is injected into an existing full map and the produced file is what gets parsed.
50static std::pair<ChimeraTK::NumericAddressedRegisterCatalogue, ChimeraTK::MetadataCatalogue> parseWithMapFormatVersion(
51 const std::string& outFile, const nlohmann::json& version) {
52 std::ifstream base("simpleJsonFile.jmap");
53 nlohmann::json map = nlohmann::json::parse(base);
54
55 if(version.is_null()) {
56 map.erase("mapFormatVersion");
57 }
58 else {
59 map["mapFormatVersion"] = version;
60 }
61
62 std::ofstream(outFile) << map.dump(2);
63 return ChimeraTK::MapFileParser::parse(outFile);
64}
65
66BOOST_AUTO_TEST_SUITE(JsonMapFileParserTestSuite)
67
68/**********************************************************************************************************************/
69/**********************************************************************************************************************/
70
71BOOST_AUTO_TEST_CASE(TestFileDoesNotExist) {
72 ChimeraTK::MapFileParser fileparser;
73 BOOST_CHECK_THROW(fileparser.parse("NonexistentFile.jmap"), ChimeraTK::logic_error);
74}
75
76/**********************************************************************************************************************/
77
78BOOST_AUTO_TEST_CASE(TestGoodMapFileParse) {
79 auto [regs, metas] = ChimeraTK::MapFileParser::parse("simpleJsonFile.jmap");
80
81 BOOST_TEST(regs.hasRegister("/SomeTopLevelRegister"));
82 BOOST_TEST(regs.hasRegister("BSP.VERSION"));
83 BOOST_TEST(regs.hasRegister("/BSP/VERSION"));
84 BOOST_TEST(regs.hasRegister("DAQ.SIMPLE2D.A"));
85
86 {
87 auto reg = regs.getBackendRegister("/SomeTopLevelRegister");
88 BOOST_TEST(reg.pathName == "/SomeTopLevelRegister");
89 BOOST_TEST(reg.nElements == 1);
90 BOOST_TEST(reg.elementPitchBits == 4 * 8);
91 BOOST_TEST(reg.bar == 0);
92 BOOST_TEST(reg.address == 32);
93 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_WRITE);
94 BOOST_REQUIRE(reg.channels.size() == 1);
95 BOOST_TEST(reg.channels[0].bitOffset == 0);
96 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
97 BOOST_TEST(reg.channels[0].width == 32);
98 BOOST_TEST(reg.channels[0].nFractionalBits == 8);
99 BOOST_TEST(reg.channels[0].signedFlag == true);
100 BOOST_TEST(reg.isBitRange == false);
101 BOOST_TEST(reg.description == "This is an example register");
102 BOOST_TEST(reg.engineeringUnit == "mV");
103 }
104 {
105 auto reg = regs.getBackendRegister("BSP.VERSION");
106 BOOST_TEST(reg.pathName == "/BSP/VERSION");
107 BOOST_TEST(reg.nElements == 1);
108 BOOST_TEST(reg.elementPitchBits == 4 * 8);
109 BOOST_TEST(reg.bar == 0);
110 BOOST_TEST(reg.address == 4);
111 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
112 BOOST_REQUIRE(reg.channels.size() == 1);
113 BOOST_TEST(reg.channels[0].bitOffset == 0);
114 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
115 BOOST_TEST(reg.channels[0].width == 32);
116 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
117 BOOST_TEST(reg.channels[0].signedFlag == false);
118 BOOST_TEST(reg.isBitRange == false);
119 }
120 {
121 auto reg = regs.getBackendRegister("BSP");
122 BOOST_TEST(reg.pathName == "/BSP");
123 BOOST_TEST(reg.nElements == 19201);
124 BOOST_TEST(reg.elementPitchBits == 4 * 8);
125 BOOST_TEST(reg.bar == 0);
126 BOOST_TEST(reg.address == 0);
127 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_WRITE);
128 BOOST_REQUIRE(reg.channels.size() == 1);
129 BOOST_TEST(reg.channels[0].bitOffset == 0);
130 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
131 BOOST_TEST(reg.channels[0].width == 32);
132 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
133 BOOST_TEST(reg.channels[0].signedFlag == false);
134 BOOST_TEST(reg.isBitRange == false);
135 }
136 {
137 auto reg = regs.getBackendRegister("APP.STATUS");
138 BOOST_TEST(reg.pathName == "/APP/STATUS");
139 BOOST_TEST(reg.nElements == 1);
140 BOOST_TEST(reg.elementPitchBits == 4 * 8);
141 BOOST_TEST(reg.bar == 2);
142 BOOST_TEST(reg.address == 0x8000);
143 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
144 BOOST_REQUIRE(reg.channels.size() == 1);
145 BOOST_TEST(reg.channels[0].bitOffset == 0);
146 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
147 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
148 BOOST_TEST(reg.channels[0].width == 32);
149 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
150 BOOST_TEST(reg.channels[0].signedFlag == false);
151 BOOST_TEST(reg.isBitRange == false);
152 }
153 {
154 BOOST_TEST(regs.hasRegister("/SCALAR"));
155 auto reg = regs.getBackendRegister("/SCALAR");
156 BOOST_TEST(reg.nElements == 1);
157 BOOST_REQUIRE(reg.channels.size() == 1);
158 BOOST_REQUIRE(reg.channels[0].selectedBy);
159 BOOST_TEST(reg.channels[0].selectedBy->regPath == "/MUX");
160 BOOST_TEST(reg.channels[0].selectedBy->val == 0);
161 }
162 {
163 auto reg = regs.getBackendRegister("APP.STATUS.ProbeLimiter");
164 BOOST_TEST(reg.pathName == "/APP/STATUS/ProbeLimiter");
165 BOOST_TEST(reg.nElements == 1);
166 BOOST_TEST(reg.elementPitchBits == 4 * 8);
167 BOOST_TEST(reg.bar == 2);
168 BOOST_TEST(reg.address == 0x8000);
169 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
170 BOOST_TEST(reg.channels.size() == 1); // for the debug printout if failing
171 BOOST_REQUIRE(reg.channels.size() == 1);
172 BOOST_TEST(reg.channels[0].bitOffset == 0);
173 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
174 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
175 BOOST_TEST(reg.channels[0].width == 1);
176 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
177 BOOST_TEST(reg.channels[0].signedFlag == false);
178 BOOST_TEST(reg.isBitRange == true);
179 }
180 {
181 auto reg = regs.getBackendRegister("APP.STATUS.ExternalInterlock");
182 BOOST_TEST(reg.pathName == "/APP/STATUS/ExternalInterlock");
183 BOOST_TEST(reg.nElements == 1);
184 BOOST_TEST(reg.elementPitchBits == 4 * 8);
185 BOOST_TEST(reg.bar == 2);
186 BOOST_TEST(reg.address == 0x8000);
187 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
188 BOOST_REQUIRE(reg.channels.size() == 1);
189 BOOST_TEST(reg.channels[0].bitOffset == 1);
190 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
191 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
192 BOOST_TEST(reg.channels[0].width == 1);
193 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
194 BOOST_TEST(reg.channels[0].signedFlag == false);
195 BOOST_TEST(reg.isBitRange == true);
196 }
197 {
198 auto reg = regs.getBackendRegister("APP.STATUS.ErrorCounter");
199 BOOST_TEST(reg.pathName == "/APP/STATUS/ErrorCounter");
200 BOOST_TEST(reg.nElements == 1);
201 BOOST_TEST(reg.elementPitchBits == 4 * 8);
202 BOOST_TEST(reg.bar == 2);
203 BOOST_TEST(reg.address == 0x8000);
204 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
205 BOOST_REQUIRE(reg.channels.size() == 1);
206 BOOST_TEST(reg.channels[0].bitOffset == 2);
207 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
208 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
209 BOOST_TEST(reg.channels[0].width == 3);
210 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
211 BOOST_TEST(reg.channels[0].signedFlag == false);
212 BOOST_TEST(reg.isBitRange == true);
213 }
214 {
215 auto reg = regs.getBackendRegister("APP.LARGE_STATUS");
216 BOOST_TEST(reg.pathName == "/APP/LARGE_STATUS");
217 BOOST_TEST(reg.nElements == 1);
218 BOOST_TEST(reg.elementPitchBits == 8 * 8);
219 BOOST_TEST(reg.bar == 2);
220 BOOST_TEST(reg.address == 0x8004);
221 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
222 BOOST_REQUIRE(reg.channels.size() == 1);
223 BOOST_TEST(reg.channels[0].bitOffset == 0);
224 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
225 BOOST_CHECK(reg.channels[0].rawType == DataType::int64);
226 BOOST_TEST(reg.channels[0].width == 64);
227 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
228 BOOST_TEST(reg.channels[0].signedFlag == false);
229 BOOST_TEST(reg.isBitRange == false);
230 }
231 {
232 auto reg = regs.getBackendRegister("APP.LARGE_STATUS.ProbeLimiter");
233 BOOST_TEST(reg.pathName == "/APP/LARGE_STATUS/ProbeLimiter");
234 BOOST_TEST(reg.nElements == 1);
235 BOOST_TEST(reg.elementPitchBits == 8 * 8);
236 BOOST_TEST(reg.bar == 2);
237 BOOST_TEST(reg.address == 0x8004);
238 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
239 BOOST_TEST(reg.channels.size() == 1); // for the debug printout if failing
240 BOOST_REQUIRE(reg.channels.size() == 1);
241 BOOST_TEST(reg.channels[0].bitOffset == 0);
242 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
243 BOOST_CHECK(reg.channels[0].rawType == DataType::int64);
244 BOOST_TEST(reg.channels[0].width == 1);
245 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
246 BOOST_TEST(reg.channels[0].signedFlag == false);
247 BOOST_TEST(reg.isBitRange == true);
248 }
249 {
250 auto reg = regs.getBackendRegister("APP.LARGE_STATUS.ExternalInterlock");
251 BOOST_TEST(reg.pathName == "/APP/LARGE_STATUS/ExternalInterlock");
252 BOOST_TEST(reg.nElements == 1);
253 BOOST_TEST(reg.elementPitchBits == 8 * 8);
254 BOOST_TEST(reg.bar == 2);
255 BOOST_TEST(reg.address == 0x8004);
256 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
257 BOOST_REQUIRE(reg.channels.size() == 1);
258 BOOST_TEST(reg.channels[0].bitOffset == 34);
259 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
260 BOOST_CHECK(reg.channels[0].rawType == DataType::int64);
261 BOOST_TEST(reg.channels[0].width == 1);
262 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
263 BOOST_TEST(reg.channels[0].signedFlag == false);
264 BOOST_TEST(reg.isBitRange == true);
265 }
266 {
267 auto reg = regs.getBackendRegister("APP.LARGE_STATUS.ErrorCounter");
268 BOOST_TEST(reg.pathName == "/APP/LARGE_STATUS/ErrorCounter");
269 BOOST_TEST(reg.nElements == 1);
270 BOOST_TEST(reg.elementPitchBits == 8 * 8);
271 BOOST_TEST(reg.bar == 2);
272 BOOST_TEST(reg.address == 0x8004);
273 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
274 BOOST_REQUIRE(reg.channels.size() == 1);
275 BOOST_TEST(reg.channels[0].bitOffset == 2);
276 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
277 BOOST_CHECK(reg.channels[0].rawType == DataType::int64);
278 BOOST_TEST(reg.channels[0].width == 3);
279 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
280 BOOST_TEST(reg.channels[0].signedFlag == false);
281 BOOST_TEST(reg.isBitRange == true);
282 }
283 {
284 auto reg = regs.getBackendRegister("APP.SomeTable");
285 BOOST_TEST(reg.pathName == "/APP/SomeTable");
286 BOOST_TEST(reg.nElements == 16384);
287 BOOST_TEST(reg.elementPitchBits == 2 * 8);
288 BOOST_TEST(reg.bar == 0);
289 BOOST_TEST(reg.address == 2048);
290 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::WRITE_ONLY);
291 BOOST_REQUIRE(reg.channels.size() == 1);
292 BOOST_TEST(reg.channels[0].bitOffset == 0);
293 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
294 BOOST_TEST(reg.channels[0].width == 14);
295 BOOST_TEST(reg.channels[0].nFractionalBits == 10);
296 BOOST_TEST(reg.channels[0].signedFlag == true);
297 BOOST_TEST(!reg.doubleBuffer.has_value());
298 BOOST_TEST(reg.isBitRange == false);
299 }
300 {
301 auto reg = regs.getBackendRegister("DAQ.CTRL");
302 BOOST_TEST(reg.pathName == "/DAQ/CTRL");
303 BOOST_TEST(reg.nElements == 16384);
304 BOOST_TEST(reg.elementPitchBits == 64 * 8);
305 BOOST_TEST(reg.bar == 13);
306 BOOST_TEST(reg.address == 0x40000);
307 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::INTERRUPT);
308 BOOST_TEST(reg.interruptId == std::vector<size_t>({3, 0, 1}), boost::test_tools::per_element());
309 BOOST_TEST(reg.doubleBuffer.has_value());
310 BOOST_TEST(reg.doubleBuffer->address == 0x40200);
311 BOOST_TEST(reg.doubleBuffer->inactiveBufferRegisterPath == "/DAQ.DOUBLE_BUF.INACTIVE_BUF_ID");
312 BOOST_TEST(reg.doubleBuffer->enableRegisterPath == "/DAQ.DOUBLE_BUF.ENA");
313
314 BOOST_REQUIRE(reg.channels.size() == 5);
315
316 for(const auto& channel : reg.channels) {
317 BOOST_TEST(!channel.selectedBy);
318 }
319
320 BOOST_TEST(reg.channels[0].bitOffset == 0);
321 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
322 BOOST_TEST(reg.channels[0].width == 16);
323 BOOST_TEST(reg.channels[0].nFractionalBits == -2);
324 BOOST_TEST(reg.channels[0].signedFlag == true);
325
326 BOOST_TEST(reg.channels[1].bitOffset == 2 * 8);
327 BOOST_CHECK(reg.channels[1].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
328 BOOST_TEST(reg.channels[1].width == 16);
329 BOOST_TEST(reg.channels[1].nFractionalBits == -2);
330 BOOST_TEST(reg.channels[1].signedFlag == true);
331
332 BOOST_TEST(reg.channels[2].bitOffset == 4 * 8);
333 BOOST_CHECK(reg.channels[2].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
334 BOOST_TEST(reg.channels[2].width == 32);
335 BOOST_TEST(reg.channels[2].nFractionalBits == 0);
336 BOOST_TEST(reg.channels[2].signedFlag == false);
337 BOOST_TEST(reg.isBitRange == false);
338 }
339 // Named channel slices of the 2D register DAQ.CTRL: each is a read-only 1D register whose address
340 // already contains the channel's byte offset, keeps the full element pitch as stride, and stores a
341 // single ChannelInfo with bitOffset 0 and the channel's raw type.
342 {
343 auto reg = regs.getBackendRegister("DAQ.CTRL.errorI");
344 BOOST_TEST(reg.pathName == "/DAQ/CTRL/errorI");
345 BOOST_TEST(reg.engineeringUnit == "bits");
346 BOOST_TEST(reg.description == "Error signal in I");
347 BOOST_TEST(reg.nElements == 16384);
348 BOOST_TEST(reg.elementPitchBits == 64 * 8);
349 BOOST_TEST(reg.bar == 13);
350 BOOST_TEST(reg.address == 0x40000);
351 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::INTERRUPT);
352 BOOST_CHECK(reg.getSupportedAccessModes().has(ChimeraTK::AccessMode::wait_for_new_data) == true);
353 BOOST_REQUIRE(reg.channels.size() == 1);
354 BOOST_TEST(reg.channels[0].bitOffset == 0);
355 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
356 BOOST_TEST(reg.channels[0].width == 16);
357 BOOST_TEST(reg.channels[0].nFractionalBits == -2);
358 BOOST_TEST(reg.channels[0].signedFlag == true);
359 BOOST_TEST(reg.channels[0].getRawType() == ChimeraTK::DataType("int16"));
360 // A slice of a non-muxed channel carries no selector
361 BOOST_TEST(!reg.channels[0].selectedBy);
362 BOOST_TEST(reg.doubleBuffer.has_value());
363 BOOST_TEST(reg.doubleBuffer->address == 0x40200);
364 BOOST_TEST(reg.doubleBuffer->enableRegisterPath == "/DAQ.DOUBLE_BUF.ENA");
365 BOOST_TEST(reg.doubleBuffer->inactiveBufferRegisterPath == "/DAQ.DOUBLE_BUF.INACTIVE_BUF_ID");
366 BOOST_TEST(reg.doubleBuffer->index == 0);
367 }
368 {
369 auto reg = regs.getBackendRegister("DAQ.CTRL.errorQ");
370 BOOST_TEST(reg.pathName == "/DAQ/CTRL/errorQ");
371 BOOST_TEST(reg.engineeringUnit == "bits");
372 BOOST_TEST(reg.description == "Error signal in Q");
373 BOOST_TEST(reg.nElements == 16384);
374 BOOST_TEST(reg.elementPitchBits == 64 * 8);
375 BOOST_TEST(reg.bar == 13);
376 BOOST_TEST(reg.address == 0x40000 + 2);
377 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::INTERRUPT);
378 BOOST_CHECK(reg.getSupportedAccessModes().has(ChimeraTK::AccessMode::wait_for_new_data) == true);
379 BOOST_REQUIRE(reg.channels.size() == 1);
380 BOOST_TEST(reg.channels[0].bitOffset == 0);
381 BOOST_TEST(reg.channels[0].width == 16);
382 BOOST_TEST(reg.channels[0].getRawType() == ChimeraTK::DataType("int16"));
383 BOOST_TEST(reg.doubleBuffer.has_value());
384 BOOST_TEST(reg.doubleBuffer->address == 0x40200 + 2);
385 BOOST_TEST(reg.doubleBuffer->enableRegisterPath == "/DAQ.DOUBLE_BUF.ENA");
386 BOOST_TEST(reg.doubleBuffer->inactiveBufferRegisterPath == "/DAQ.DOUBLE_BUF.INACTIVE_BUF_ID");
387 BOOST_TEST(reg.doubleBuffer->index == 0);
388 }
389 // A double-buffered named channel slice inherits the parent register's double-buffer configuration. The slice
390 // additionally gets its own BUF0/BUF1 buffer-view registers, identical to the parent's but with the channel byte
391 // offset folded into both buffer addresses.
392 {
393 auto reg = regs.getBackendRegister("DAQ.CTRL.errorI.BUF0");
394 BOOST_TEST(reg.pathName == "/DAQ/CTRL/errorI/BUF0");
395 BOOST_TEST(reg.nElements == 16384);
396 BOOST_TEST(reg.elementPitchBits == 64 * 8);
397 BOOST_TEST(reg.bar == 13);
398 BOOST_TEST(reg.address == 0x40000);
399 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
400 BOOST_CHECK(!reg.doubleBuffer.has_value());
401 BOOST_REQUIRE(reg.channels.size() == 1);
402 BOOST_TEST(reg.channels[0].bitOffset == 0);
403 BOOST_TEST(reg.channels[0].width == 16);
404 BOOST_TEST(reg.channels[0].getRawType() == ChimeraTK::DataType("int16"));
405 }
406 {
407 auto reg = regs.getBackendRegister("DAQ.CTRL.errorI.BUF1");
408 BOOST_TEST(reg.pathName == "/DAQ/CTRL/errorI/BUF1");
409 BOOST_TEST(reg.nElements == 16384);
410 BOOST_TEST(reg.elementPitchBits == 64 * 8);
411 BOOST_TEST(reg.bar == 13);
412 BOOST_TEST(reg.address == 0x40200);
413 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
414 BOOST_CHECK(!reg.doubleBuffer.has_value());
415 BOOST_REQUIRE(reg.channels.size() == 1);
416 BOOST_TEST(reg.channels[0].bitOffset == 0);
417 BOOST_TEST(reg.channels[0].width == 16);
418 BOOST_TEST(reg.channels[0].getRawType() == ChimeraTK::DataType("int16"));
419 }
420 // A channel with non-zero byte offset folds that offset into both buffer addresses.
421 {
422 auto reg0 = regs.getBackendRegister("DAQ.CTRL.errorQ.BUF0");
423 BOOST_TEST(reg0.address == 0x40000 + 2);
424 auto reg1 = regs.getBackendRegister("DAQ.CTRL.errorQ.BUF1");
425 BOOST_TEST(reg1.address == 0x40200 + 2);
426 }
427 // Named channel slice of a non-interrupt 2D register stays read-only and does not advertise
428 // wait_for_new_data.
429 {
430 auto reg = regs.getBackendRegister("DAQ.SIMPLE2D.A");
431 BOOST_TEST(reg.pathName == "/DAQ/SIMPLE2D/A");
432 BOOST_TEST(reg.bar == 13);
433 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
434 BOOST_CHECK(reg.getSupportedAccessModes().has(ChimeraTK::AccessMode::raw) == true);
435 BOOST_CHECK(reg.getSupportedAccessModes().has(ChimeraTK::AccessMode::wait_for_new_data) == false);
436 BOOST_REQUIRE(reg.channels.size() == 1);
437 BOOST_TEST(reg.channels[0].bitOffset == 0);
438 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
439 BOOST_TEST(reg.channels[0].width == 16);
440 BOOST_TEST(reg.channels[0].getRawType() == ChimeraTK::DataType("int16"));
441 // A slice of a non-muxed SIMPLE2D channel carries no selector
442 BOOST_TEST(!reg.channels[0].selectedBy);
443 }
444 {
445 auto reg = regs.getBackendRegister("DAQ.FD");
446 BOOST_TEST(reg.pathName == "/DAQ/FD");
447 BOOST_TEST(reg.nElements == 16384);
448 BOOST_TEST(reg.elementPitchBits == 64 * 8);
449 BOOST_TEST(reg.bar == 13);
450 BOOST_TEST(reg.address == 0x81000);
451 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::INTERRUPT);
452 BOOST_TEST(reg.interruptId == std::vector<size_t>({0}), boost::test_tools::per_element());
453
454 BOOST_REQUIRE(reg.channels.size() == 4);
455
456 BOOST_TEST(reg.channels[0].bitOffset == 0);
457 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
458 BOOST_TEST(reg.channels[0].width == 16);
459 BOOST_TEST(reg.channels[0].nFractionalBits == -2);
460 BOOST_TEST(reg.channels[0].signedFlag == true);
461 BOOST_REQUIRE(reg.channels[0].selectedBy);
462 BOOST_TEST(reg.channels[0].selectedBy->regPath == "/DAQ/MUX_SEL");
463 BOOST_TEST(reg.channels[0].selectedBy->val == 0);
464
465 BOOST_TEST(reg.channels[1].bitOffset == 0);
466 BOOST_CHECK(reg.channels[1].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
467 BOOST_TEST(reg.channels[1].width == 32);
468 BOOST_TEST(reg.channels[1].nFractionalBits == 0);
469 BOOST_TEST(reg.channels[1].signedFlag == true);
470 BOOST_REQUIRE(reg.channels[1].selectedBy);
471 BOOST_TEST(reg.channels[1].selectedBy->regPath == "/DAQ/MUX_SEL");
472 BOOST_TEST(reg.channels[1].selectedBy->val == 1);
473
474 BOOST_TEST(reg.channels[2].bitOffset == 2 * 8);
475 BOOST_CHECK(reg.channels[2].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
476 BOOST_TEST(reg.channels[2].width == 16);
477 BOOST_TEST(reg.channels[2].nFractionalBits == -2);
478 BOOST_TEST(reg.channels[2].signedFlag == true);
479 BOOST_REQUIRE(reg.channels[2].selectedBy);
480 BOOST_TEST(reg.channels[2].selectedBy->regPath == "/DAQ/MUX_SEL");
481 BOOST_TEST(reg.channels[2].selectedBy->val == 0);
482
483 BOOST_TEST(reg.channels[3].bitOffset == 4 * 8);
484 BOOST_CHECK(reg.channels[3].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
485 BOOST_TEST(reg.channels[3].width == 32);
486 BOOST_TEST(reg.channels[3].nFractionalBits == 0);
487 BOOST_TEST(reg.channels[3].signedFlag == true);
488 BOOST_REQUIRE(reg.channels[3].selectedBy);
489 BOOST_TEST(reg.channels[3].selectedBy->regPath == "/DAQ/MUX_SEL");
490 BOOST_TEST(reg.channels[3].selectedBy->val == 1);
491 BOOST_TEST(reg.isBitRange == false);
492 }
493 {
494 auto reg = regs.getBackendRegister("DAQ.DOUBLE_BUF.ENA");
495 BOOST_TEST(reg.pathName == "/DAQ/DOUBLE_BUF/ENA");
496 BOOST_TEST(reg.nElements == 3);
497 BOOST_TEST(reg.elementPitchBits == 4 * 8);
498 BOOST_TEST(reg.bar == 0);
499 BOOST_TEST(reg.address == 1236);
500 BOOST_REQUIRE(reg.channels.size() == 1);
501 BOOST_TEST(reg.channels[0].bitOffset == 0);
502 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
503 BOOST_TEST(reg.channels[0].width == 1);
504 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
505 BOOST_TEST(reg.channels[0].signedFlag == false);
506 BOOST_TEST(reg.isBitRange == false);
507 }
508 {
509 auto reg = regs.getBackendRegister("DAQ.DOUBLE_BUF.INACTIVE_BUF_ID");
510 BOOST_TEST(reg.pathName == "/DAQ/DOUBLE_BUF/INACTIVE_BUF_ID");
511 BOOST_TEST(reg.nElements == 3);
512 BOOST_TEST(reg.elementPitchBits == 4 * 8);
513 BOOST_TEST(reg.bar == 0);
514 BOOST_TEST(reg.address == 1240);
515 BOOST_REQUIRE(reg.channels.size() == 1);
516 BOOST_TEST(reg.channels[0].bitOffset == 0);
517 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
518 BOOST_TEST(reg.channels[0].width == 1);
519 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
520 BOOST_TEST(reg.channels[0].signedFlag == false);
521 BOOST_TEST(reg.isBitRange == false);
522 }
523 {
524 auto reg = regs.getBackendRegister("DAQ.FD.BUF0");
525 BOOST_TEST(reg.pathName == "/DAQ/FD/BUF0");
526 BOOST_TEST(reg.nElements == 16384);
527 BOOST_TEST(reg.elementPitchBits == 64 * 8);
528 BOOST_TEST(reg.bar == 13);
529 BOOST_TEST(reg.address == 0x81000);
530
531 BOOST_REQUIRE(reg.channels.size() == 4);
532
533 BOOST_TEST(reg.channels[0].bitOffset == 0);
534 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
535 BOOST_TEST(reg.channels[0].width == 16);
536 BOOST_TEST(reg.channels[0].nFractionalBits == -2);
537 BOOST_TEST(reg.channels[0].signedFlag == true);
538 BOOST_REQUIRE(reg.channels[0].selectedBy);
539 BOOST_TEST(reg.channels[0].selectedBy->regPath == "/DAQ/MUX_SEL");
540 BOOST_TEST(reg.channels[0].selectedBy->val == 0);
541
542 BOOST_TEST(reg.channels[1].bitOffset == 0);
543 BOOST_CHECK(reg.channels[1].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
544 BOOST_TEST(reg.channels[1].width == 32);
545 BOOST_TEST(reg.channels[1].nFractionalBits == 0);
546 BOOST_TEST(reg.channels[1].signedFlag == true);
547 BOOST_REQUIRE(reg.channels[0].selectedBy);
548 BOOST_TEST(reg.channels[1].selectedBy->regPath == "/DAQ/MUX_SEL");
549 BOOST_TEST(reg.channels[1].selectedBy->val == 1);
550
551 BOOST_TEST(reg.channels[2].bitOffset == 2 * 8);
552 BOOST_CHECK(reg.channels[2].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
553 BOOST_TEST(reg.channels[2].width == 16);
554 BOOST_TEST(reg.channels[2].nFractionalBits == -2);
555 BOOST_TEST(reg.channels[2].signedFlag == true);
556 BOOST_REQUIRE(reg.channels[0].selectedBy);
557 BOOST_TEST(reg.channels[2].selectedBy->regPath == "/DAQ/MUX_SEL");
558 BOOST_TEST(reg.channels[2].selectedBy->val == 0);
559
560 BOOST_TEST(reg.channels[3].bitOffset == 4 * 8);
561 BOOST_CHECK(reg.channels[3].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
562 BOOST_TEST(reg.channels[3].width == 32);
563 BOOST_TEST(reg.channels[3].nFractionalBits == 0);
564 BOOST_TEST(reg.channels[3].signedFlag == true);
565 BOOST_REQUIRE(reg.channels[0].selectedBy);
566 BOOST_TEST(reg.channels[3].selectedBy->regPath == "/DAQ/MUX_SEL");
567 BOOST_TEST(reg.channels[3].selectedBy->val == 1);
568 BOOST_TEST(reg.isBitRange == false);
569 }
570 {
571 auto reg = regs.getBackendRegister("DAQ.FD.BUF1");
572 BOOST_TEST(reg.pathName == "/DAQ/FD/BUF1");
573 BOOST_TEST(reg.nElements == 16384);
574 BOOST_TEST(reg.elementPitchBits == 64 * 8);
575 BOOST_TEST(reg.bar == 13);
576
577 BOOST_REQUIRE(reg.channels.size() == 4);
578
579 BOOST_TEST(reg.channels[0].bitOffset == 0);
580 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
581 BOOST_TEST(reg.channels[0].width == 16);
582 BOOST_TEST(reg.channels[0].nFractionalBits == -2);
583 BOOST_TEST(reg.channels[0].signedFlag == true);
584 BOOST_REQUIRE(reg.channels[0].selectedBy);
585 BOOST_TEST(reg.channels[0].selectedBy->regPath == "/DAQ/MUX_SEL");
586 BOOST_TEST(reg.channels[0].selectedBy->val == 0);
587
588 BOOST_TEST(reg.channels[1].bitOffset == 0);
589 BOOST_CHECK(reg.channels[1].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
590 BOOST_TEST(reg.channels[1].width == 32);
591 BOOST_TEST(reg.channels[1].nFractionalBits == 0);
592 BOOST_TEST(reg.channels[1].signedFlag == true);
593 BOOST_REQUIRE(reg.channels[0].selectedBy);
594 BOOST_TEST(reg.channels[1].selectedBy->regPath == "/DAQ/MUX_SEL");
595 BOOST_TEST(reg.channels[1].selectedBy->val == 1);
596
597 BOOST_TEST(reg.channels[2].bitOffset == 2 * 8);
598 BOOST_CHECK(reg.channels[2].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
599 BOOST_TEST(reg.channels[2].width == 16);
600 BOOST_TEST(reg.channels[2].nFractionalBits == -2);
601 BOOST_TEST(reg.channels[2].signedFlag == true);
602 BOOST_REQUIRE(reg.channels[0].selectedBy);
603 BOOST_TEST(reg.channels[2].selectedBy->regPath == "/DAQ/MUX_SEL");
604 BOOST_TEST(reg.channels[2].selectedBy->val == 0);
605
606 BOOST_TEST(reg.channels[3].bitOffset == 4 * 8);
607 BOOST_CHECK(reg.channels[3].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
608 BOOST_TEST(reg.channels[3].width == 32);
609 BOOST_TEST(reg.channels[3].nFractionalBits == 0);
610 BOOST_TEST(reg.channels[3].signedFlag == true);
611 BOOST_REQUIRE(reg.channels[0].selectedBy);
612 BOOST_TEST(reg.channels[3].selectedBy->regPath == "/DAQ/MUX_SEL");
613 BOOST_TEST(reg.channels[3].selectedBy->val == 1);
614 BOOST_TEST(reg.isBitRange == false);
615 }
616 {
617 auto reg = regs.getBackendRegister("DAQ.MUX_SEL");
618 BOOST_TEST(reg.pathName == "/DAQ/MUX_SEL");
619 BOOST_TEST(reg.nElements == 1);
620 BOOST_TEST(reg.elementPitchBits == 4 * 8);
621 BOOST_TEST(reg.bar == 0);
622 BOOST_TEST(reg.address == 1244);
623 BOOST_REQUIRE(reg.channels.size() == 1);
624 BOOST_TEST(reg.channels[0].bitOffset == 0);
625 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
626 BOOST_TEST(reg.channels[0].width == 2);
627 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
628 BOOST_TEST(reg.channels[0].signedFlag == false);
629 BOOST_TEST(reg.isBitRange == false);
630 }
631 {
632 auto reg = regs.getBackendRegister("DAQ.MACRO_PULSE_NUMBER");
633 BOOST_TEST(reg.pathName == "/DAQ/MACRO_PULSE_NUMBER");
634 BOOST_TEST(reg.nElements == 1);
635 BOOST_TEST(reg.elementPitchBits == 4 * 8);
636 BOOST_TEST(reg.bar == 13);
637 BOOST_TEST(reg.address == 0x8103C);
638 BOOST_REQUIRE(reg.channels.size() == 1);
639 BOOST_TEST(reg.channels[0].bitOffset == 0);
640 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
641 BOOST_TEST(reg.channels[0].width == 32);
642 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
643 BOOST_TEST(reg.channels[0].signedFlag == false);
644 BOOST_TEST(reg.isBitRange == false);
645 }
646 {
647 auto reg = regs.getBackendRegister("BSP.SOME_INFO");
648 BOOST_TEST(reg.pathName == "/BSP/SOME_INFO");
649 BOOST_TEST(reg.nElements == 1);
650 BOOST_TEST(reg.elementPitchBits == 40 * 8);
651 BOOST_TEST(reg.bar == 0);
652 BOOST_TEST(reg.address == 8);
653 BOOST_REQUIRE(reg.channels.size() == 1);
654 BOOST_TEST(reg.channels[0].bitOffset == 0);
655 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::ASCII);
656 BOOST_TEST(reg.channels[0].width == 32);
657 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
658 BOOST_TEST(reg.channels[0].signedFlag == false);
659 BOOST_TEST(reg.isBitRange == false);
660 }
661 {
662 auto reg = regs.getBackendRegister("APP.SomeFloat");
663 BOOST_TEST(reg.pathName == "/APP/SomeFloat");
664 BOOST_TEST(reg.nElements == 1);
665 BOOST_TEST(reg.elementPitchBits == 4 * 8);
666 BOOST_TEST(reg.bar == 0);
667 BOOST_TEST(reg.address == 4096);
668 BOOST_REQUIRE(reg.channels.size() == 1);
669 BOOST_TEST(reg.channels[0].bitOffset == 0);
670 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::IEEE754);
671 BOOST_TEST(reg.channels[0].width == 32);
672 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
673 BOOST_TEST(reg.channels[0].signedFlag == true);
674 BOOST_TEST(reg.isBitRange == false);
675 }
676 {
677 auto reg = regs.getBackendRegister("BSP.VOID_INTERRUPT_0");
678 BOOST_TEST(reg.pathName == "/BSP/VOID_INTERRUPT_0");
679 BOOST_TEST(reg.nElements == 0);
680 BOOST_REQUIRE(reg.channels.size() == 1);
681 BOOST_TEST(reg.channels[0].bitOffset == 0);
682 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::VOID);
683 BOOST_TEST(reg.channels[0].width == 0);
684 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
685 BOOST_TEST(reg.channels[0].signedFlag == false);
686 BOOST_TEST(reg.isBitRange == false);
687 }
688
689 BOOST_TEST(metas.getNumberOfMetadata() == 6);
690
691 BOOST_TEST(metas.getMetadata("mapfileRevision") == "1.8.3-0-gdeadbeef");
692 BOOST_TEST(metas.getMetadata("someRandomEntry") == "some random value");
693
694 BOOST_TEST(metas.getMetadata("![0]") == R"({"INTC":{"path":"DAQ","version":1}})");
695 BOOST_TEST(metas.getMetadata("![3]") == R"({"INTC":{"options":["MER"],"path":"MY_INTC","version":1}})");
696 BOOST_TEST(metas.getMetadata("![3,0]") == R"({"INTC":{"options":[],"path":"MY_INTC.SUB0","version":1}})");
697 BOOST_TEST(metas.getMetadata("![3,1]") == R"({"INTC":{"options":["MER"],"path":"MY_INTC.SUB1","version":1}})");
698
699 auto loi = regs.getListOfInterrupts();
700 BOOST_TEST(loi.size() == 3);
701 BOOST_CHECK(loi.find({0}) != loi.end());
702 BOOST_CHECK(loi.find({3, 0, 1}) != loi.end());
703 BOOST_CHECK(loi.find({1}) != loi.end());
704}
705
706/**********************************************************************************************************************/
707
708// An interrupt handler entry is passed through as metadata regardless of its controller type and data structure: a
709// backend-specific "MyINTC" controller carrying an arbitrary structure that is incompatible with the fixed "INTC"
710// controller parses and reaches the metadata, normalised only per the common rules (underscore-prefixed keys dropped,
711// a missing 'version' defaulted to 1) and re-serialised with json::dump() without the 'subhandler' member.
712BOOST_AUTO_TEST_CASE(TestBackendSpecificInterruptHandler) {
713 // Build a map with a single interrupt handler entry whose "MyINTC" controller carries a nested object and
714 // unexpected value types (a boolean, a floating point, an array with mixed and null entries) that the old fixed
715 // "INTC" controller would have dropped. Underscore-prefixed keys (a "_comment" at the top level and inside the
716 // controller) must be dropped by the common normalisation.
717 nlohmann::json entry = {{"_comment", "dropped top level"},
718 {"MyINTC",
719 {{"path", "MY_INTC"}, {"cfg", {{"mode", "triggered"}, {"count", 3}, {"flag", true}, {"ratio", 1.5}}},
720 {"list", nlohmann::json::array({"a", 2, nullptr})}, {"_comment", "dropped in controller"}}}};
721 nlohmann::json subEntry = {{"_comment", "dropped in subhandler"},
722 {"MyINTC", {{"path", "MY_INTC.SUB0"}, {"extra", {{"deep", {{"k", "v"}}}}}}}};
723 entry["subhandler"]["0"] = subEntry;
724
725 nlohmann::json map;
726 map["mapFormatVersion"] = "1.0";
727 map["interruptHandler"]["4"] = entry;
728 map["metadata"] = nlohmann::json::object();
729 map["addressSpace"] = nlohmann::json::object();
730
731 std::string tmpFile = "backendSpecificInterruptHandler_" + std::to_string(getpid()) + ".jmap";
732 std::ofstream(tmpFile) << map.dump(2);
733 auto [regs, metas] = ChimeraTK::MapFileParser::parse(tmpFile);
734
735 // Expected metadata values: the entry minus its 'subhandler' member, normalised per the common rules (underscore
736 // keys dropped, a missing 'version' defaulted to 1), re-serialised with json::dump().
737 nlohmann::json expectedTop = entry;
738 expectedTop.erase("_comment");
739 expectedTop.erase("subhandler");
740 expectedTop["MyINTC"].erase("_comment");
741 expectedTop["MyINTC"]["version"] = 1;
742 BOOST_TEST(metas.getMetadata("![4]") == expectedTop.dump());
743
744 // The subhandler recursion appends its id component and normalises its own entry in the same way.
745 nlohmann::json expectedSub = subEntry;
746 expectedSub.erase("_comment");
747 expectedSub["MyINTC"]["version"] = 1;
748 BOOST_TEST(metas.getMetadata("![4,0]") == expectedSub.dump());
749
750 std::remove(tmpFile.c_str());
751}
752
753/**********************************************************************************************************************/
754
755// Bit ranges inside the named channels of a 2D register (simpleJsonFile.jmap DAQ.CTRL). A channel with a
756// `children` dictionary yields the parent word slice (byte-aligned, unshifted, not a bit range) plus one
757// read-only bit-range slice per child. The slices inherit the parent 2D register's address, stride, interrupt
758// access and double-buffer settings (including the BUF0/BUF1 buffer views).
759BOOST_AUTO_TEST_CASE(TestNamedChannelBitRangeChildren) {
760 auto [regs, metas] = ChimeraTK::MapFileParser::parse("simpleJsonFile.jmap");
761
762 // The parent channel slice /DAQ/CTRL/status spans the whole channel word: byte-aligned, unshifted, not a bit
763 // range. It inherits nElements (16384) and the 64-byte pitch from the parent 2D register.
764 {
765 auto reg = regs.getBackendRegister("DAQ.CTRL.status");
766 BOOST_TEST(reg.pathName == "/DAQ/CTRL/status");
767 BOOST_TEST(reg.nElements == 16384);
768 BOOST_TEST(reg.elementPitchBits == 64 * 8);
769 // DAQ.CTRL is a DMA register, so its (and the slices') BAR is the DMA pseudo-BAR 13. It is at DMA offset
770 // 0x40000 and channel 'status' sits at byte offset 4.
771 BOOST_TEST(reg.bar == 13);
772 BOOST_TEST(reg.address == 0x40004);
773 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::INTERRUPT);
774 BOOST_REQUIRE(reg.channels.size() == 1);
775 BOOST_TEST(reg.channels[0].bitOffset == 0);
776 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
777 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
778 BOOST_TEST(reg.channels[0].width == 32);
779 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
780 BOOST_TEST(reg.channels[0].signedFlag == false);
781 BOOST_TEST(reg.isBitRange == false);
782 BOOST_REQUIRE(reg.doubleBuffer);
783 BOOST_TEST(reg.doubleBuffer->address == 0x40204);
784 BOOST_TEST(regs.hasRegister("DAQ.CTRL.status.BUF0"));
785 BOOST_TEST(regs.hasRegister("DAQ.CTRL.status.BUF1"));
786 }
787 // First bit-field child: bit range at bit offset 0, width 1.
788 {
789 auto reg = regs.getBackendRegister("DAQ.CTRL.status.ProbeLimiter");
790 BOOST_TEST(reg.pathName == "/DAQ/CTRL/status/ProbeLimiter");
791 BOOST_TEST(reg.nElements == 16384);
792 BOOST_TEST(reg.elementPitchBits == 64 * 8);
793 BOOST_TEST(reg.bar == 13);
794 BOOST_TEST(reg.address == 0x40004);
795 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::INTERRUPT);
796 BOOST_REQUIRE(reg.channels.size() == 1);
797 BOOST_TEST(reg.channels[0].bitOffset == 0);
798 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
799 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
800 BOOST_TEST(reg.channels[0].width == 1);
801 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
802 BOOST_TEST(reg.channels[0].signedFlag == false);
803 BOOST_TEST(reg.isBitRange == true);
804 BOOST_REQUIRE(reg.doubleBuffer);
805 BOOST_TEST(reg.doubleBuffer->address == 0x40204);
806 BOOST_TEST(regs.hasRegister("DAQ.CTRL.status.ProbeLimiter.BUF0"));
807 BOOST_TEST(regs.hasRegister("DAQ.CTRL.status.ProbeLimiter.BUF1"));
808 }
809 // Second bit-field child at bit offset 1, width 1.
810 {
811 auto reg = regs.getBackendRegister("DAQ.CTRL.status.ExternalInterlock");
812 BOOST_TEST(reg.pathName == "/DAQ/CTRL/status/ExternalInterlock");
813 BOOST_TEST(reg.nElements == 16384);
814 BOOST_TEST(reg.elementPitchBits == 64 * 8);
815 BOOST_TEST(reg.bar == 13);
816 BOOST_TEST(reg.address == 0x40004);
817 BOOST_REQUIRE(reg.channels.size() == 1);
818 BOOST_TEST(reg.channels[0].bitOffset == 1);
819 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
820 BOOST_TEST(reg.channels[0].width == 1);
821 BOOST_TEST(reg.isBitRange == true);
822 }
823 // Third child carries a fixedPoint representation: fractionalBits and isSigned are taken from the child.
824 {
825 auto reg = regs.getBackendRegister("DAQ.CTRL.status.ErrorCounter");
826 BOOST_TEST(reg.pathName == "/DAQ/CTRL/status/ErrorCounter");
827 BOOST_TEST(reg.address == 0x40004);
828 BOOST_REQUIRE(reg.channels.size() == 1);
829 BOOST_TEST(reg.channels[0].bitOffset == 2);
830 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
831 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
832 BOOST_TEST(reg.channels[0].width == 3);
833 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
834 BOOST_TEST(reg.channels[0].signedFlag == false);
835 BOOST_TEST(reg.isBitRange == true);
836 }
837 // VectorSum_I has a single child 'I' at bit offset 0, width 18.
838 {
839 auto reg = regs.getBackendRegister("DAQ.CTRL.VectorSum_I.I");
840 BOOST_TEST(reg.pathName == "/DAQ/CTRL/VectorSum_I/I");
841 BOOST_TEST(reg.address == 0x40008);
842 BOOST_REQUIRE(reg.channels.size() == 1);
843 BOOST_TEST(reg.channels[0].bitOffset == 0);
844 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
845 BOOST_TEST(reg.channels[0].width == 18);
846 BOOST_TEST(reg.isBitRange == true);
847 }
848 // VectorSum_Q has a single child 'Q' at bit offset 2, width 18.
849 {
850 auto reg = regs.getBackendRegister("DAQ.CTRL.VectorSum_Q.Q");
851 BOOST_TEST(reg.pathName == "/DAQ/CTRL/VectorSum_Q/Q");
852 BOOST_TEST(reg.address == 0x4000a);
853 BOOST_REQUIRE(reg.channels.size() == 1);
854 BOOST_TEST(reg.channels[0].bitOffset == 2);
855 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
856 BOOST_TEST(reg.channels[0].width == 18);
857 BOOST_TEST(reg.isBitRange == true);
858 }
859}
860
861/**********************************************************************************************************************/
862
863// A channel that is itself a single bit range (bitShift/width in its own representation, e.g. channel B of
864// bitRangeChannels.jmap) yields a bit-range slice: bitOffset 16, width 8, isBitRange true, read-only.
865BOOST_AUTO_TEST_CASE(TestNamedChannelDirectBitRange) {
866 auto [regs, metas] = ChimeraTK::MapFileParser::parse("bitRangeChannels.jmap");
867
868 // Channel B is a direct bit range: bits 16..23 of every sample word.
869 {
870 auto reg = regs.getBackendRegister("TEST.BR.B");
871 BOOST_TEST(reg.pathName == "/TEST/BR/B");
872 BOOST_TEST(reg.nElements == 4);
873 BOOST_TEST(reg.elementPitchBits == 8 * 8);
874 BOOST_TEST(reg.bar == 13);
875 BOOST_TEST(reg.address == 0x4004);
876 BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::READ_ONLY);
877 BOOST_REQUIRE(reg.channels.size() == 1);
878 BOOST_TEST(reg.channels[0].bitOffset == 16);
879 BOOST_CHECK(reg.channels[0].dataType == NumericAddressedRegisterInfo::Type::FIXED_POINT);
880 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
881 BOOST_TEST(reg.channels[0].width == 8);
882 BOOST_TEST(reg.channels[0].nFractionalBits == 0);
883 BOOST_TEST(reg.channels[0].signedFlag == false);
884 BOOST_TEST(reg.isBitRange == true);
885 }
886 // Contrast with channel A of the same register: exposing the full word it is not a bit range.
887 {
888 auto reg = regs.getBackendRegister("TEST.BR.A");
889 BOOST_TEST(reg.address == 0x4000);
890 BOOST_TEST(reg.isBitRange == false);
891 BOOST_TEST(reg.channels[0].bitOffset == 0);
892 BOOST_TEST(reg.channels[0].width == 32);
893 }
894 // The bit-field child slices of channel A remain bit ranges.
895 {
896 auto reg = regs.getBackendRegister("TEST.BR.A.Hi");
897 BOOST_TEST(reg.channels[0].bitOffset == 8);
898 BOOST_TEST(reg.isBitRange == true);
899 }
900}
901
902/**********************************************************************************************************************/
903
904// A channel child that is not a proper bit range of the channel word must be ignored while the supported sibling
905// children are still created. 'Good' (bitShift 0, width 8, within a 32-bit word) is a valid bit range; 'Whole'
906// spans the entire channel word (bitShift 0, width 32) and 'Overflow' extends past it (bitShift 28, width 8).
907BOOST_AUTO_TEST_CASE(TestNamedChannelBitRangeIgnoredChildren) {
908 auto [regs, metas] = ChimeraTK::MapFileParser::parse("bitRangeChildCases.jmap");
909
910 // The valid child slice is present.
911 {
912 auto reg = regs.getBackendRegister("CHILD.A.Good");
913 BOOST_TEST(reg.pathName == "/CHILD/A/Good");
914 BOOST_REQUIRE(reg.channels.size() == 1);
915 BOOST_TEST(reg.channels[0].bitOffset == 0);
916 BOOST_CHECK(reg.channels[0].rawType == DataType::int32);
917 BOOST_TEST(reg.channels[0].width == 8);
918 BOOST_TEST(reg.isBitRange == true);
919 }
920 // The unsupported children must not be present.
921 BOOST_CHECK(!regs.hasRegister("CHILD.A.Whole"));
922 BOOST_CHECK(!regs.hasRegister("CHILD.A.Overflow"));
923 // The parent channel slice still exists.
924 BOOST_CHECK(regs.hasRegister("CHILD.A"));
925}
926
927/**********************************************************************************************************************/
928
929// A channel literally named 'BUF0' in a double-buffered 2D register collides with the parent's auto-generated BUF0
930// buffer view. This is a map authoring error and must fail fast with a logic_error at parse time. The colliding
931// channel is injected into a copy of simpleJsonFile.jmap (the double-buffered DAQ.CTRL register) with nlohmann::json,
932// written to a temporary file, and that file is parsed.
933BOOST_AUTO_TEST_CASE(TestNamedChannelBitRangeBuf0CollisionThrows) {
934 // Load the existing jmap file and inject a channel named 'BUF0' into the double-buffered DAQ.CTRL register.
935 std::ifstream in("simpleJsonFile.jmap");
936 nlohmann::json doc;
937 in >> doc;
938 doc["addressSpace"]["DAQ"]["children"]["CTRL"]["channels"]["BUF0"] = {{"bytesPerElement", 4}, {"offset", 6},
939 {"representation", {{"type", "fixedPoint"}, {"width", 16}, {"fractionalBits", 0}}}};
940 std::string tmpFile = "bitRangeBuf0Collision_" + std::to_string(getpid()) + ".jmap";
941 std::ofstream out(tmpFile);
942 out << doc.dump();
943 out.close();
944
946 BOOST_CHECK_THROW(parser.parse(tmpFile), ChimeraTK::logic_error);
947
948 std::remove(tmpFile.c_str());
949}
950
951// A bit-field child literally named 'BUF0' of a double-buffered channel collides with the parent channel slice's
952// auto-generated BUF0 buffer view. This is a map authoring error and must fail fast with a logic_error at parse
953// time, rather than silently skipping the child slice. The colliding child is injected into a copy of
954// simpleJsonFile.jmap (whose DAQ.CTRL 'status' channel already carries children) with nlohmann::json, written to a
955// temporary file, and that file is parsed.
956BOOST_AUTO_TEST_CASE(TestNamedChannelBitRangeChildBuf0CollisionThrows) {
957 // Load the existing jmap file and add a child named 'BUF0' to the 'status' channel of DAQ.CTRL.
958 std::ifstream in("simpleJsonFile.jmap");
959 nlohmann::json doc;
960 in >> doc;
961 doc["addressSpace"]["DAQ"]["children"]["CTRL"]["channels"]["status"]["children"]["BUF0"] = {
962 {"representation", {{"bitShift", 8}, {"width", 8}}}};
963 std::string tmpFile = "bitRangeChildBuf0Collision_" + std::to_string(getpid()) + ".jmap";
964 std::ofstream out(tmpFile);
965 out << doc.dump();
966 out.close();
967
969 BOOST_CHECK_THROW(parser.parse(tmpFile), ChimeraTK::logic_error);
970
971 std::remove(tmpFile.c_str());
972}
973
974/**********************************************************************************************************************/
975
976// A bit-field child slice of a muxed channel inherits the channel's selectedBy condition (fixture
977// simpleJsonFile.jmap DAQ.MUXED_WITH_STATUS, which combines a per-channel 'selectedBy' with a 'children'
978// dictionary). Every child slice carries the same register+value selector as the parent channel slice.
979BOOST_AUTO_TEST_CASE(TestNamedChannelBitRangeMuxedChildSelectedBy) {
980 auto [regs, metas] = ChimeraTK::MapFileParser::parse("simpleJsonFile.jmap");
981
982 // The muxed parent channel slice inherits the selectedBy condition.
983 {
984 auto reg = regs.getBackendRegister("DAQ.MUXED_WITH_STATUS.StatusCh0");
985 BOOST_REQUIRE(reg.channels.size() == 1);
986 BOOST_REQUIRE(reg.channels[0].selectedBy);
987 BOOST_TEST(reg.channels[0].selectedBy->regPath == "/DAQ/MUX_SEL");
988 BOOST_TEST(reg.channels[0].selectedBy->val == 2);
989 BOOST_TEST(reg.isBitRange == false);
990 }
991 // Each bit-field child slice of the muxed channel inherits the channel's selectedBy condition.
992 {
993 auto reg = regs.getBackendRegister("DAQ.MUXED_WITH_STATUS.StatusCh0.ProbeLimiter");
994 BOOST_REQUIRE(reg.channels.size() == 1);
995 BOOST_TEST(reg.channels[0].bitOffset == 0);
996 BOOST_TEST(reg.channels[0].width == 1);
997 BOOST_TEST(reg.isBitRange == true);
998 BOOST_REQUIRE(reg.channels[0].selectedBy);
999 BOOST_TEST(reg.channels[0].selectedBy->regPath == "/DAQ/MUX_SEL");
1000 BOOST_TEST(reg.channels[0].selectedBy->val == 2);
1001 }
1002 {
1003 auto reg = regs.getBackendRegister("DAQ.MUXED_WITH_STATUS.StatusCh0.ErrorCounter");
1004 BOOST_REQUIRE(reg.channels.size() == 1);
1005 BOOST_TEST(reg.channels[0].bitOffset == 2);
1006 BOOST_TEST(reg.channels[0].width == 3);
1007 BOOST_TEST(reg.isBitRange == true);
1008 BOOST_REQUIRE(reg.channels[0].selectedBy);
1009 BOOST_TEST(reg.channels[0].selectedBy->regPath == "/DAQ/MUX_SEL");
1010 BOOST_TEST(reg.channels[0].selectedBy->val == 2);
1011 }
1012}
1013
1014/**********************************************************************************************************************/
1015
1016// A double-buffered register whose two buffers lie on different BARs is not supported and must fail fast with a
1017// logic_error at parse time. The fixture is a 2D / named-channel register, directly covering the channel-slice case
1018// that prompted the check (DoubleBufferingInfo::fill runs before any slices are derived).
1019BOOST_AUTO_TEST_CASE(TestDoubleBufferDiffBarThrows) {
1021 BOOST_CHECK_THROW(parser.parse("simpleJsonFile.diffBar.jmap"), ChimeraTK::logic_error);
1022}
1023
1024/**********************************************************************************************************************/
1025
1026// ChannelInfo::operator== (and !=) must include the selectedByRegister/selectedByValue members. Two channel infos
1027// that differ only in their selector must compare unequal.
1028BOOST_AUTO_TEST_CASE(TestChannelInfoEqualitySelectedBy) {
1031
1032 // NOTE: ChannelInfo is an aggregate whose scalar members carry no default member initializers, so they must be set
1033 // explicitly before comparing (default initialization leaves them indeterminate).
1034 auto initChannelInfo = [](NumericAddressedRegisterInfo::ChannelInfo& c) {
1035 c.bitOffset = 0;
1036 c.dataType = NumericAddressedRegisterInfo::Type::FIXED_POINT;
1037 c.width = 16;
1038 c.nFractionalBits = 0;
1039 c.signedFlag = true;
1041 c.selectedBy = std::nullopt;
1042 };
1043 initChannelInfo(a);
1044 initChannelInfo(b);
1045
1046 // Two identically initialized infos are equal (both unconditional: empty register, default value).
1047 BOOST_CHECK(a == b);
1048 BOOST_CHECK(!(a != b));
1049
1050 BOOST_REQUIRE(!a.selectedBy);
1051 BOOST_REQUIRE(!b.selectedBy);
1052
1053 // Differing only in selectedByRegister -> unequal.
1054 b.selectedBy.emplace(RegisterPath("/DAQ/MUX_SEL"), 0);
1055 BOOST_CHECK(a != b);
1056 BOOST_CHECK(!(a == b));
1057
1058 // Same register again -> equal.
1059 a.selectedBy.emplace(RegisterPath("/DAQ/MUX_SEL"), 0);
1060 BOOST_CHECK(a == b);
1061
1062 // Differing only in selectedByValue -> unequal.
1063 b.selectedBy->val = 1;
1064 BOOST_CHECK(a != b);
1065 BOOST_CHECK(!(a == b));
1066}
1067
1068/**********************************************************************************************************************/
1069
1070// selectedBy with only 'register', no 'value'. Desired semantics (documented): both fields are required, so the
1071// parser must reject the map with std::logic_error (a missing 'value' must not silently default). NOTE: the current
1072// lax deserializer does not yet enforce this; this test documents the desired behaviour.
1073BOOST_AUTO_TEST_CASE(TestSelectedByMissingValue) {
1074 nlohmann::json sel{{"register", "COLLISION.MUX"}}; // missing 'value'
1075 BOOST_CHECK_THROW(parseInjectedSelectedByFault("selectedByMissingValue.jmap", sel), ChimeraTK::logic_error);
1076}
1077
1078/**********************************************************************************************************************/
1079
1080// selectedBy with only 'value', no 'register'. Desired semantics (documented): the parser must reject the map
1081// because both fields are required. NOTE: expected std::logic_error, not guaranteed by the current lax deserializer.
1082BOOST_AUTO_TEST_CASE(TestSelectedByMissingRegister) {
1083 nlohmann::json sel{{"value", 0}}; // missing 'register'
1084 BOOST_CHECK_THROW(parseInjectedSelectedByFault("selectedByMissingRegister.jmap", sel), ChimeraTK::logic_error);
1085}
1086
1087/**********************************************************************************************************************/
1088
1089// S1: 'selectedBy' referencing a selector register that does not exist in the catalogue must be rejected.
1090BOOST_AUTO_TEST_CASE(TestSelectedBySelectorNotFound) {
1091 nlohmann::json map;
1092 map["mapFormatVersion"] = "1.0";
1093 map["interruptHandler"] = nlohmann::json::object();
1094 map["metadata"] = nlohmann::json::object();
1095 // APP.DATA gates on a selector register 'APP.NONEXISTENT' that is never declared.
1096 map["addressSpace"]["APP"]["children"]["DATA"] = {{"numberOfElements", 1}, {"access", "RO"},
1097 {"selectedBy", {{"register", "APP.NONEXISTENT"}, {"value", 1}}}, {"address", {{"channel", 0}, {"offset", 4}}},
1098 {"representation", {{"width", 32}}}};
1099 std::string tmpFile = "selectedBySelectorNotFound_" + std::to_string(getpid()) + ".jmap";
1100 std::ofstream(tmpFile) << map.dump(2);
1101 BOOST_CHECK_THROW(ChimeraTK::MapFileParser::parse(tmpFile), ChimeraTK::logic_error);
1102}
1103
1104// S2: a selector register that is itself conditionally enabled by a 'selectedBy' of its own must be rejected: a
1105// selector drives the gate of another register and must therefore be readable unconditionally.
1106BOOST_AUTO_TEST_CASE(TestSelectedBySelectorItselfGated) {
1107 nlohmann::json map;
1108 map["mapFormatVersion"] = "1.0";
1109 map["interruptHandler"] = nlohmann::json::object();
1110 map["metadata"] = nlohmann::json::object();
1111 // APP.SEL is itself gated by APP.MASTER_SEL, and APP.DATA gates on APP.SEL. The selector APP.SEL must not be gated.
1112 map["addressSpace"]["APP"]["children"]["SEL"] = {{"numberOfElements", 1}, {"access", "RO"},
1113 {"selectedBy", {{"register", "APP.MASTER_SEL"}, {"value", 1}}}, {"address", {{"channel", 0}, {"offset", 0}}},
1114 {"representation", {{"width", 32}}}};
1115 map["addressSpace"]["APP"]["children"]["MASTER_SEL"] = {{"numberOfElements", 1}, {"access", "RO"},
1116 {"address", {{"channel", 0}, {"offset", 8}}}, {"representation", {{"width", 32}}}};
1117 map["addressSpace"]["APP"]["children"]["DATA"] = {{"numberOfElements", 1}, {"access", "RO"},
1118 {"selectedBy", {{"register", "APP.SEL"}, {"value", 1}}}, {"address", {{"channel", 0}, {"offset", 4}}},
1119 {"representation", {{"width", 32}}}};
1120 std::string tmpFile = "selectedBySelectorItselfGated_" + std::to_string(getpid()) + ".jmap";
1121 std::ofstream(tmpFile) << map.dump(2);
1122 BOOST_CHECK_THROW(ChimeraTK::MapFileParser::parse(tmpFile), ChimeraTK::logic_error);
1123}
1124
1125/**********************************************************************************************************************/
1126// Helper used by the selectedBy read-only restriction tests (PR1-PR5): build a minimal map in a temp file and parse
1127// it. The map carries a selector register APP.SEL and a register APP.DATA whose 'selectedBy' points to it; the
1128// register's access is controlled by the caller. If 'access' is empty the 'access' member is omitted, defaulting to
1129// READ_WRITE at parse time.
1130static ChimeraTK::NumericAddressedRegisterCatalogue parseSelectedByAccessFixture(
1131 const std::string& access, const nlohmann::json& dataExtra = nlohmann::json::object()) {
1132 nlohmann::json map;
1133 map["mapFormatVersion"] = "1.0";
1134 map["interruptHandler"] = nlohmann::json::object();
1135 map["metadata"] = nlohmann::json::object();
1136 map["addressSpace"]["APP"]["children"]["SEL"] = {
1137 {"numberOfElements", 1}, {"address", {{"channel", 0}, {"offset", 0}}}, {"representation", {{"width", 32}}}};
1138 nlohmann::json reg = {{"numberOfElements", 1}, {"selectedBy", {{"register", "APP.SEL"}, {"value", 1}}},
1139 {"address", {{"channel", 0}, {"offset", 4}}}, {"representation", {{"width", 32}}}};
1140 if(!access.empty()) {
1141 reg["access"] = access;
1142 }
1143 for(auto it = dataExtra.begin(); it != dataExtra.end(); ++it) {
1144 reg[it.key()] = it.value();
1145 }
1146 map["addressSpace"]["APP"]["children"]["DATA"] = reg;
1147 std::string tmpFile = "selectedByAccess_" + std::to_string(getpid()) + ".jmap";
1148 std::ofstream(tmpFile) << map.dump(2);
1149 return ChimeraTK::MapFileParser::parse(tmpFile).first;
1150}
1151
1152/**********************************************************************************************************************/
1153
1154// PR1: 'selectedBy' on a register with Access::READ_WRITE must be rejected as a parsing error.
1155BOOST_AUTO_TEST_CASE(TestSelectedByReadWriteRejected) {
1156 BOOST_CHECK_THROW(parseSelectedByAccessFixture("RW"), ChimeraTK::logic_error);
1157}
1158
1159// PR2: 'selectedBy' on a register with Access::WRITE_ONLY must be rejected as a parsing error.
1160BOOST_AUTO_TEST_CASE(TestSelectedByWriteOnlyRejected) {
1161 BOOST_CHECK_THROW(parseSelectedByAccessFixture("WO"), ChimeraTK::logic_error);
1162}
1163
1164// PR3: 'selectedBy' on a register with Access::READ_ONLY is accepted.
1165BOOST_AUTO_TEST_CASE(TestSelectedByReadOnlyAccepted) {
1166 auto regs = parseSelectedByAccessFixture("RO");
1167 auto reg = regs.getBackendRegister("/APP/DATA");
1168 BOOST_REQUIRE(reg.channels[0].selectedBy);
1169 BOOST_TEST(reg.channels[0].selectedBy->regPath == "/APP/SEL");
1170 BOOST_TEST(reg.channels[0].selectedBy->val == 1);
1171}
1172
1173// PR4: 'selectedBy' on a register with Access::INTERRUPT is accepted. An interrupt register is expressed via
1174// 'triggeredByInterrupt' (which implies read-only) instead of 'access'.
1175BOOST_AUTO_TEST_CASE(TestSelectedByInterruptAccepted) {
1176 auto regs = parseSelectedByAccessFixture("", {{"triggeredByInterrupt", nlohmann::json::array({0})}});
1177 auto reg = regs.getBackendRegister("/APP/DATA");
1178 BOOST_REQUIRE(reg.channels[0].selectedBy);
1179 BOOST_TEST(reg.channels[0].selectedBy->val == 1);
1180}
1181
1182/**********************************************************************************************************************/
1183
1184// Helper used by PR5: build a map with a module 'MOD' that declares 'selectedBy', and a single descendant register
1185// '<childName>' under it. The child's access is controlled by 'childAccess' (empty => READ_WRITE default).
1186static ChimeraTK::NumericAddressedRegisterCatalogue parseSelectedByInheritedAccessFixture(
1187 const std::string& childAccess, const std::string& childName) {
1188 nlohmann::json map;
1189 map["mapFormatVersion"] = "1.0";
1190 map["interruptHandler"] = nlohmann::json::object();
1191 map["metadata"] = nlohmann::json::object();
1192 map["addressSpace"]["APP"]["children"]["SEL"] = {
1193 {"numberOfElements", 1}, {"address", {{"channel", 0}, {"offset", 0}}}, {"representation", {{"width", 32}}}};
1194 nlohmann::json child = {
1195 {"numberOfElements", 1}, {"address", {{"channel", 0}, {"offset", 4}}}, {"representation", {{"width", 32}}}};
1196 if(!childAccess.empty()) {
1197 child["access"] = childAccess;
1198 }
1199 map["addressSpace"]["APP"]["children"]["MOD"] = {
1200 {"selectedBy", {{"register", "APP.SEL"}, {"value", 1}}}, {"children", {{childName, child}}}};
1201 std::string tmpFile = "selectedByInheritedAccess_" + std::to_string(getpid()) + ".jmap";
1202 std::ofstream(tmpFile) << map.dump(2);
1203 return ChimeraTK::MapFileParser::parse(tmpFile).first;
1204}
1205
1206// PR5: a module/parent declaring 'selectedBy' must be rejected if any descendant register inheriting it is writable;
1207// the error points to the parent declaration, not the child.
1208BOOST_AUTO_TEST_CASE(TestSelectedByInheritedWritableDescendantRejected) {
1209 // A writable (default READ_WRITE) descendant inheriting the module's selectedBy is rejected.
1210 BOOST_CHECK_THROW(parseSelectedByInheritedAccessFixture("", "CHILD"), ChimeraTK::logic_error);
1211 // An explicitly writable descendant is rejected as well.
1212 BOOST_CHECK_THROW(parseSelectedByInheritedAccessFixture("WO", "CHILD"), ChimeraTK::logic_error);
1213 // A read-only descendant satisfies the constraint.
1214 auto regs = parseSelectedByInheritedAccessFixture("RO", "CHILD");
1215 auto reg = regs.getBackendRegister("/APP/MOD/CHILD");
1216 BOOST_REQUIRE(reg.channels[0].selectedBy);
1217 BOOST_TEST(reg.channels[0].selectedBy->val == 1);
1218}
1219
1220/**********************************************************************************************************************/
1221
1222// selectedBy on a non-2D (scalar) register is supported: it makes the single register conditional, so the
1223// register's single channel must carry the selector.
1224BOOST_AUTO_TEST_CASE(TestSelectedByOnScalar) {
1225 auto [regs, metas] = ChimeraTK::MapFileParser::parse("simpleJsonFile.jmap");
1226}
1227
1228/**********************************************************************************************************************/
1229
1230// A 'selectedBy' declared on a module or parent register is inherited by all descendant registers/channels that lack
1231// their own declaration; the nearest ancestor's declaration wins for a node that has its own.
1232BOOST_AUTO_TEST_CASE(TestSelectedByInheritance) {
1233 auto [regs, metas] = ChimeraTK::MapFileParser::parse("selectedByInheritance.jmap");
1234
1235 // Helpers: assert the single channel of a scalar/1D register carries the given selector (or none).
1236 auto checkRegSelectedBy = [](const NumericAddressedRegisterInfo& reg, const std::string& expectedReg,
1237 std::optional<int64_t> expectedVal) {
1238 BOOST_REQUIRE(reg.channels.size() == 1);
1239 const auto& sb = reg.channels[0].selectedBy;
1240 if(expectedVal.has_value()) {
1241 BOOST_REQUIRE(sb.has_value());
1242 BOOST_TEST(sb->regPath == expectedReg);
1243 BOOST_TEST(sb->val == *expectedVal);
1244 }
1245 else {
1246 BOOST_CHECK(!sb.has_value());
1247 }
1248 };
1249 // Helpers: assert each channel of a 2D register, in natural (byte-offset) order.
1250 auto check2DChannelsSelectedBy = [](const NumericAddressedRegisterInfo& reg,
1251 std::initializer_list<std::pair<std::string, int64_t>> expected) {
1252 BOOST_REQUIRE(reg.channels.size() == expected.size());
1253 size_t i = 0;
1254 for(const auto& [expectedReg, expectedVal] : expected) {
1255 const auto& sb = reg.channels[i].selectedBy;
1256 BOOST_REQUIRE(sb.has_value());
1257 BOOST_TEST(sb->regPath == expectedReg);
1258 BOOST_TEST(sb->val == expectedVal);
1259 ++i;
1260 }
1261 };
1262
1263 // Module inheritance. REG_A inherits the module default; REG_B keeps its own (override).
1264 checkRegSelectedBy(regs.getBackendRegister("/INHERIT/REG_A"), "/APP/OUTPUT_SELECT", 3);
1265 checkRegSelectedBy(regs.getBackendRegister("/INHERIT/REG_B"), "/CTRL/MODE", 9);
1266
1267 // Nested inheritance (module -> submodule -> leaf). SUB carries no selectedBy, so LEAF inherits from INHERIT.
1268 checkRegSelectedBy(regs.getBackendRegister("/INHERIT/SUB/LEAF"), "/APP/OUTPUT_SELECT", 3);
1269
1270 // 2D module default. All channels of a contained 2D register without own declarations inherit the module default.
1271 check2DChannelsSelectedBy(
1272 regs.getBackendRegister("/INHERIT/CHANA"), {{"/APP/OUTPUT_SELECT", 3}, {"/APP/OUTPUT_SELECT", 3}});
1273
1274 // Channel override. D1 has its own per-channel selectedBy; D0 inherits the module default.
1275 check2DChannelsSelectedBy(
1276 regs.getBackendRegister("/INHERIT/CHANB"), {{"/APP/OUTPUT_SELECT", 3}, {"/CTRL/CHAN_SEL", 7}});
1277
1278 // Register-module dual role. Its selectedBy applies to its own register AND to its descendant.
1279 checkRegSelectedBy(regs.getBackendRegister("/INHERIT/REG2D_OWN"), "/CTRL/DUAL", 5);
1280 checkRegSelectedBy(regs.getBackendRegister("/INHERIT/REG2D_OWN/CHILD"), "/CTRL/DUAL", 5);
1281
1282 // Register-level selectedBy directly on a 2D register (no module) is valid and becomes the default for all channels.
1283 check2DChannelsSelectedBy(regs.getBackendRegister("/REG2D_TOP"), {{"/APP/TOP_SEL", 11}, {"/APP/TOP_SEL", 11}});
1284
1285 // A leaf with no ancestor carrying selectedBy stays unconditional (no inheritance).
1286 checkRegSelectedBy(regs.getBackendRegister("/LEAF_TOP"), "", std::nullopt);
1287
1288 // The 1D channel slices of an inherited 2D register also carry the effective selector.
1289 checkRegSelectedBy(regs.getBackendRegister("/INHERIT/CHANA/C0"), "/APP/OUTPUT_SELECT", 3);
1290 checkRegSelectedBy(regs.getBackendRegister("/INHERIT/CHANA/C1"), "/APP/OUTPUT_SELECT", 3);
1291 checkRegSelectedBy(regs.getBackendRegister("/REG2D_TOP/E0"), "/APP/TOP_SEL", 11);
1292}
1293
1294/**********************************************************************************************************************/
1295
1296// All mapFormatVersion checks: a file declaring the supported version "1.0" parses without error, and any other
1297// version throws ChimeraTK::logic_error.
1298BOOST_AUTO_TEST_CASE(TestMapFormatVersionWithPatch) {
1299 BOOST_CHECK_NO_THROW(parseWithMapFormatVersion("mapFormatVersionSupported.jmap", "1.0"));
1300 BOOST_CHECK_NO_THROW(parseWithMapFormatVersion("mapFormatVersionLeadingZerosMinor.jmap", "1.00"));
1301 BOOST_CHECK_NO_THROW(parseWithMapFormatVersion("mapFormatVersionLeadingZerosMajor.jmap", "01.0"));
1302 BOOST_CHECK_NO_THROW(parseWithMapFormatVersion("mapFormatVersionLeadingZeros.jmap", "01.00"));
1303
1304 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionOld.jmap", "0.0.1"), ChimeraTK::logic_error);
1305 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionMismatchedMinor.jmap", "1.1"), ChimeraTK::logic_error);
1306 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionMismatchedMajor.jmap", "2.0"), ChimeraTK::logic_error);
1307 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionSingle.jmap", "1"), ChimeraTK::logic_error);
1308 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionMissingMajor.jmap", ".0"), ChimeraTK::logic_error);
1309 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionMissingMinor.jmap", "1."), ChimeraTK::logic_error);
1310 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionMajorNoDot.jmap", "1x.0"), ChimeraTK::logic_error);
1311 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionNonDecimal.jmap", "1.0 "), ChimeraTK::logic_error);
1312 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionOversizedMinor.jmap", "1.999999999999999999999999"),
1314 BOOST_CHECK_THROW(parseWithMapFormatVersion("mapFormatVersionOversizedMajor.jmap", "999999999999999999999999.0"),
1316 BOOST_CHECK_THROW(
1317 parseWithMapFormatVersion("mapFormatVersionMissing.jmap", nlohmann::json(nullptr)), ChimeraTK::logic_error);
1318 BOOST_CHECK_THROW(
1319 parseWithMapFormatVersion("mapFormatVersionWrongType.jmap", nlohmann::json(1.0)), ChimeraTK::logic_error);
1320}
1321
1322/**********************************************************************************************************************/
1323
1324BOOST_AUTO_TEST_SUITE_END()
A class to describe which of the supported data types is used.
@ int16
Signed 16 bit integer.
@ int32
Signed 32 bit integer.
@ int64
Signed 64 bit integer.
static std::pair< NumericAddressedRegisterCatalogue, MetadataCatalogue > parse(const std::string &fileName)
Performs parsing of specified MAP file, resulting in catalogue objects describing all registers and m...
NumericAddressedRegisterInfo getBackendRegister(const RegisterPath &registerPathName) const override
Note: Override this function if backend has "hidden" registers which are not added to the map and hen...
std::vector< ChannelInfo > channels
Define per-channel information (bit interpretation etc.), 1D/scalars have exactly one entry.
Class to store a register path name.
Exception thrown when a logic error has occured.
Definition Exception.h:51
This header collects some conversion routines which should be useful both for ApplicationCore Python ...
@ wait_for_new_data
Make any read blocking until new data has arrived since the last read.
@ raw
Raw access: disable any possible conversion from the original hardware data type into the given UserT...
std::string to_string(const std::string &v)
BOOST_AUTO_TEST_CASE(TestFileDoesNotExist)