ChimeraTK-DeviceAccess 03.31.00
Loading...
Searching...
No Matches
testDoubleBufferAccessor.cc
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
2// SPDX-License-Identifier: LGPL-3.0-or-later
3
4#define BOOST_TEST_MODULE testDoubleBufferAccessor
5#include "Device.h"
6#include "DummyBackend.h"
8
9#include <boost/test/unit_test.hpp>
10
11#include <future>
12
13using namespace ChimeraTK;
14
15// ------------------------------------------------------------
16// Test that a full read cycle completes successfully.
17// Observable behaviour: after read(), the accessor holds data
18// and the enable register is left in the enabled state (value 1).
19BOOST_AUTO_TEST_CASE(test_full_read_cycle) {
21 device.open("(dummy?map=simpleJsonFile.jmap)");
22
23 // Get double-buffer accessor through the normal public API.
24 // DAQ.MACRO_PULSE_NUMBER is a simple 1-element uint32 register
25 // which uses double-buffering with index 1.
26 auto accessor = device.getOneDRegisterAccessor<int>("/DAQ/MACRO_PULSE_NUMBER", 1, 0);
27
28 // Perform a full read cycle through the public API
29 accessor.read();
30
31 // After the read cycle, we have valid data
32 BOOST_CHECK(accessor.dataValidity() != ChimeraTK::DataValidity::faulty);
33}
34
35// ------------------------------------------------------------
36// Test that concurrent reads on the same double-buffered register
37// complete successfully. The transfer lock ensures sequential
38// access to the handshake registers, so no data corruption occurs.
39BOOST_AUTO_TEST_CASE(test_concurrent_reads_same_register) {
41 device.open("(dummy?map=simpleJsonFile.jmap)");
42
43 // Two accessors on the same register
44 auto accessor1 = device.getOneDRegisterAccessor<int>("/DAQ/MACRO_PULSE_NUMBER", 1, 0);
45 auto accessor2 = device.getOneDRegisterAccessor<int>("/DAQ/MACRO_PULSE_NUMBER", 1, 0);
46
47 // Read concurrently from two threads. Both must complete without
48 // deadlock or data corruption.
49 auto future = std::async(std::launch::async, [&] { accessor1.read(); });
50 accessor2.read();
51
52 auto status = future.wait_for(std::chrono::milliseconds(200));
53 BOOST_CHECK(status == std::future_status::ready);
54}
55
56// ------------------------------------------------------------
57// Test that two registers sharing the same double buffering handshake
58// (index 1: DAQ.MACRO_PULSE_NUMBER and DAQ.FD both use DAQ.DOUBLE_BUF.ENA[1])
59// can be read concurrently without issues.
60BOOST_AUTO_TEST_CASE(test_shared_handshake_two_registers) {
62 device.open("(dummy?map=simpleJsonFile.jmap)");
63
64 // Two accessors with the same handshake index (index 1)
65 auto mpnAccessor1 = device.getOneDRegisterAccessor<int>("/DAQ/MACRO_PULSE_NUMBER", 1, 0);
66 auto mpnAccessor2 = device.getOneDRegisterAccessor<int>("/DAQ/MACRO_PULSE_NUMBER", 1, 0);
67
68 // Read concurrently from two threads
69 auto future = std::async(std::launch::async, [&] { mpnAccessor1.read(); });
70 mpnAccessor2.read();
71
72 auto status = future.wait_for(std::chrono::milliseconds(200));
73 BOOST_CHECK(status == std::future_status::ready);
74}
75
76// ------------------------------------------------------------
77// Test that buffer selection works with INACTIVE_BUF_ID=1.
78// This exercises the _currentBuffer==1 branch.
79BOOST_AUTO_TEST_CASE(test_buffer_selection_current_buffer_1) {
81 device.open("(dummy?map=simpleJsonFile.jmap)");
82
83 // Write INACTIVE_BUF_ID[index=1] = 1
84 auto inactiveBuf = device.getOneDRegisterAccessor<uint32_t>("/DAQ/DOUBLE_BUF/INACTIVE_BUF_ID", 1, 1);
85 inactiveBuf[0] = 1;
86 inactiveBuf.write();
87
88 auto accessor = device.getOneDRegisterAccessor<int>("/DAQ/MACRO_PULSE_NUMBER", 1, 0);
89
90 // Do a full read cycle with the _currentBuffer==1 path
91 accessor.read();
92
93 // Must not crash, and must have valid data
94 BOOST_CHECK(accessor.dataValidity() != ChimeraTK::DataValidity::faulty);
95}
96
97// ------------------------------------------------------------
98// Helper that simulates the firmware finishing a buffer and raising the
99// interrupt. An interrupt-triggered read must return that freshly finished
100// buffer (the inactive one at the time of the read).
102 public:
104 : dummy(openDeviceAndGetDummy(device)),
105 accessor(device.getOneDRegisterAccessor<uint32_t>("/DAQ/ASYNC_DBLBUF", 1, 0, {AccessMode::wait_for_new_data})),
106 enable(dummy.get(), "DAQ/DOUBLE_BUF", "ENA"), inactive(dummy.get(), "DAQ/DOUBLE_BUF", "INACTIVE_BUF_ID"),
107 buffer0(dummy.get(), "DAQ/ASYNC_DBLBUF", "BUF0"), buffer1(dummy.get(), "DAQ/ASYNC_DBLBUF", "BUF1") {
108 // Enable double buffering for index 2.
109 enable[2] = 1;
110 }
111
112 // Simulate the firmware finishing a buffer: fill the freshly finished buffer
113 // and point INACTIVE_BUF_ID at it, then raise the interrupt.
114 void firmwareFinishesBuffer(uint32_t value, uint32_t newInactiveBuffer) {
115 if(newInactiveBuffer == 1) {
116 buffer0 = value;
117 }
118 else {
119 buffer1 = value;
120 }
121 inactive[2] = newInactiveBuffer;
122 dummy->triggerInterrupt(1);
123 }
124
126 boost::shared_ptr<DummyBackend> dummy;
132
133 private:
134 static boost::shared_ptr<DummyBackend> openDeviceAndGetDummy(Device& dev) {
135 dev.open("(dummy?map=simpleJsonFile.jmap)");
136 auto backend = boost::dynamic_pointer_cast<DummyBackend>(dev.getBackend());
137 if(!backend) {
138 BOOST_FAIL("Device did not produce a DummyBackend");
139 }
140 return backend;
141 }
142};
143
144// ------------------------------------------------------------
145// Test that an interrupt-triggered wait_for_new_data read of the full
146// double-buffered register returns the buffer the firmware just finished, over
147// several consecutive buffer swaps.
148BOOST_AUTO_TEST_CASE(TestInterruptDrivenReadReturnsFinishedBuffer) {
151
152 // An initial value is delivered when the async domain is activated.
153 BOOST_CHECK(f.accessor.readNonBlocking());
155
156 // No further data before the firmware completes the next buffer.
157 BOOST_CHECK(!f.accessor.readNonBlocking());
158
159 // Swap 1: firmware finishes buffer0 (=100) and flips to buffer1.
160 f.firmwareFinishesBuffer(100, 1);
161 BOOST_CHECK(f.accessor.readNonBlocking());
162 BOOST_CHECK_EQUAL(f.accessor[0], 100);
163 BOOST_CHECK(!f.accessor.readNonBlocking());
164
165 // Swap 2: firmware finishes buffer1 (=200) and flips to buffer0.
166 BOOST_CHECK(!f.accessor.readNonBlocking());
167 f.firmwareFinishesBuffer(200, 0);
168 BOOST_CHECK(f.accessor.readNonBlocking());
169 BOOST_CHECK_EQUAL(f.accessor[0], 200);
170 BOOST_CHECK(!f.accessor.readNonBlocking());
171
172 // Swap 3: firmware finishes buffer0 (=300) and flips to buffer1.
173 BOOST_CHECK(!f.accessor.readNonBlocking());
174 f.firmwareFinishesBuffer(300, 1);
175 BOOST_CHECK(f.accessor.readNonBlocking());
176 BOOST_CHECK_EQUAL(f.accessor[0], 300);
177 BOOST_CHECK(!f.accessor.readNonBlocking());
178
179 // Swap 4: firmware finishes buffer1 (=400) and flips to buffer0.
180 BOOST_CHECK(!f.accessor.readNonBlocking());
181 f.firmwareFinishesBuffer(400, 0);
182 BOOST_CHECK(f.accessor.readNonBlocking());
183 BOOST_CHECK_EQUAL(f.accessor[0], 400);
184 BOOST_CHECK(!f.accessor.readNonBlocking());
185
186 f.device.close();
187}
188
189// ------------------------------------------------------------
190// Test that the double-buffer handshake stays enabled after interrupt-driven
191// reads: ENA must still hold 1 after a wait_for_new_data read cycle.
192BOOST_AUTO_TEST_CASE(TestInterruptDrivenReadKeepsHandshakeEnabled) {
195
196 // An initial value is delivered when the async domain is activated.
197 BOOST_CHECK(f.accessor.readNonBlocking());
198
199 // No data before the firmware completes the next buffer.
200 BOOST_CHECK(!f.accessor.readNonBlocking());
201
202 f.firmwareFinishesBuffer(500, 0);
203 BOOST_CHECK(f.accessor.readNonBlocking());
204 BOOST_CHECK_EQUAL(f.accessor[0], 500);
205 BOOST_CHECK(!f.accessor.readNonBlocking());
206
207 // The handshake must not disable swapping, so ENA stays enabled.
208 BOOST_CHECK(f.enable[2] == 1);
209
210 f.device.close();
211}
DummyRegisterAccessor< uint32_t > buffer0
DummyRegisterAccessor< uint32_t > enable
DummyRegisterAccessor< uint32_t > inactive
DummyRegisterAccessor< uint32_t > buffer1
boost::shared_ptr< DummyBackend > dummy
void firmwareFinishesBuffer(uint32_t value, uint32_t newInactiveBuffer)
OneDRegisterAccessor< uint32_t > accessor
Class allows to read/write registers from device.
Definition Device.h:39
void close()
Close the device.
Definition Device.cc:66
boost::shared_ptr< DeviceBackend > getBackend()
Obtain the backend.
Definition Device.cc:111
OneDRegisterAccessor< UserType > getOneDRegisterAccessor(const RegisterPath &registerPathName, size_t numberOfWords=0, size_t wordOffsetInRegister=0, const AccessModeFlags &flags=AccessModeFlags({})) const
Get a OneDRegisterAccessor object for the given register.
Definition Device.h:276
void activateAsyncRead() noexcept
Activate asyncronous read for all transfer elements where AccessMode::wait_for_new_data is set.
Definition Device.cc:91
void open(std::string const &aliasName)
Open a device by the given alias name from the DMAP file.
Definition Device.cc:58
Register accessor for accessing single word or 1D array registers internally of a DummyBackend implem...
Accessor class to read and write registers transparently by using the accessor object like a vector o...
DataValidity dataValidity() const
Return current validity of the data.
bool readNonBlocking()
Read the next value, if available in the input buffer.
This header collects some conversion routines which should be useful both for ApplicationCore Python ...
@ faulty
The data is considered valid.
BOOST_AUTO_TEST_CASE(test_full_read_cycle)
ctk::Device device