diff --git a/include/JsonExtensions.h b/include/JsonExtensions.h new file mode 100644 index 00000000..b7403e8d --- /dev/null +++ b/include/JsonExtensions.h @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once + +#include + +#include + +namespace nlohmann { + + template + struct adl_serializer> { + static void to_json(json& j, const std::optional& opt) { + if(opt.has_value()) { + j = *opt; + } + else { + j = nullptr; + } + } + + static void from_json(const json& j, std::optional& opt) { + if(j.is_null()) { + opt = std::nullopt; + } + else { + opt = j.get(); + } + } + }; + +} // namespace nlohmann diff --git a/include/NDDoubleBufferAccessorDecorator.h b/include/NDDoubleBufferAccessorDecorator.h new file mode 100644 index 00000000..cec50113 --- /dev/null +++ b/include/NDDoubleBufferAccessorDecorator.h @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project +// SPDX-License-Identifier: LGPL-3.0-or-later +#pragma once + +#include "NDRegisterAccessor.h" +#include "NDRegisterAccessorDecorator.h" +#include "NumericAddressedBackend.h" +#include "RegisterInfo.h" +#include "TransferElement.h" + +#include + +namespace ChimeraTK { + + template + class NumericDoubleBufferAccessorDecorator : public NDRegisterAccessorDecorator { + public: + using ChimeraTK::NDRegisterAccessorDecorator::buffer_2D; + using ChimeraTK::NDRegisterAccessorDecorator::_target; + NumericDoubleBufferAccessorDecorator(const boost::shared_ptr>& target, + std::optional doubleBufferConfig, + const boost::shared_ptr& backend, + std::shared_ptr controlState); + + void doPreRead(TransferType type) override; + + void doReadTransferSynchronously() override; + + void doPostRead(TransferType type, bool hasNewData) override; + + [[nodiscard]] bool isWriteable() const override { return false; } + + void doPreWrite(TransferType, VersionNumber) override { + throw ChimeraTK::logic_error("NumericAddressBackend DoubleBufferPlugin: Writing is not allowed atm."); + } + + void doPostWrite(TransferType, VersionNumber) override { + // do not throw here again + } + // below functions are needed for TransferGroup to work + std::vector> getHardwareAccessingElements() override; + + std::list> getInternalElements() override { return {}; } + + void replaceTransferElement(boost::shared_ptr /* newElement */) override { + // do nothing, we do not support merging of DoubleBufferAccessorDecorators + } + [[nodiscard]] bool mayReplaceOther(const boost::shared_ptr& other) const override; + + private: + // we know that plugin exists at least as long as any register (of the catalogue) refers to it, + // so no shared_ptr required here + std::optional _doubleBufferInfo; + boost::shared_ptr _backend; + std::shared_ptr _controlState; + boost::shared_ptr> _secondBufferReg; + boost::shared_ptr> _enableDoubleBufferReg; + boost::shared_ptr> _currentBufferNumberReg; + uint32_t _currentBuffer{0}; + }; +} // namespace ChimeraTK diff --git a/include/NumericAddressedBackend.h b/include/NumericAddressedBackend.h index 333ca139..08ebb5a5 100644 --- a/include/NumericAddressedBackend.h +++ b/include/NumericAddressedBackend.h @@ -145,6 +145,11 @@ namespace ChimeraTK { template std::pair getAsyncDomainInitialValue(size_t asyncDomainId); + struct DoubleBufferControlState { + std::mutex mutex; + size_t readerCount = 0; + }; + protected: /* * Register catalogue. A reference is used here which is filled from _registerMapPointer in the constructor to allow @@ -178,6 +183,8 @@ namespace ChimeraTK { /** We have to remember this in case a new async::Domain is created after calling ActivateAsyncRead. */ std::atomic_bool _asyncIsActive{false}; + + std::unordered_map> _controlStateMap; }; /********************************************************************************************************************/ diff --git a/include/NumericAddressedRegisterCatalogue.h b/include/NumericAddressedRegisterCatalogue.h index a25be03c..9cb0818f 100644 --- a/include/NumericAddressedRegisterCatalogue.h +++ b/include/NumericAddressedRegisterCatalogue.h @@ -50,6 +50,13 @@ namespace ChimeraTK { [[nodiscard]] DataType getRawType() const; /**< Return raw type matching the given width */ }; + struct DoubleBufferInfo { + uint64_t offset; + RegisterPath enableRegisterPath; + RegisterPath inactiveBufferRegisterPath; + uint32_t index; + }; + /** * Constructor to set all data members for scalar/1D registers. They all have default values, so this also acts as * default constructor. @@ -57,14 +64,15 @@ namespace ChimeraTK { explicit NumericAddressedRegisterInfo(RegisterPath const& pathName_ = {}, uint32_t nElements_ = 0, uint64_t address_ = 0, uint32_t nBytes_ = 0, uint64_t bar_ = 0, uint32_t width_ = 32, int32_t nFractionalBits_ = 0, bool signedFlag_ = true, Access dataAccess_ = Access::READ_WRITE, - Type dataType_ = Type::FIXED_POINT, std::vector interruptId_ = {}); + Type dataType_ = Type::FIXED_POINT, std::vector interruptId_ = {}, + std::optional doubleBuffer_ = std::nullopt); /** * Constructor to set all data members for 2D registers. */ NumericAddressedRegisterInfo(RegisterPath const& pathName_, uint64_t bar_, uint64_t address_, uint32_t nElements_, uint32_t elementPitchBits_, std::vector channelInfo_, Access dataAccess_, - std::vector interruptId_); + std::vector interruptId_, std::optional doubleBuffer_ = std::nullopt); NumericAddressedRegisterInfo(const NumericAddressedRegisterInfo&) = default; @@ -115,7 +123,7 @@ namespace ChimeraTK { Access registerAccess; /**< Data access direction: Read, write, read and write or interrupt */ std::vector interruptId; - + std::optional doubleBuffer; /** Define per-channel information (bit interpretation etc.), 1D/scalars have exactly one entry. */ std::vector channels; diff --git a/src/JsonMapFileParser.cc b/src/JsonMapFileParser.cc index ed1b17ec..a62454f7 100644 --- a/src/JsonMapFileParser.cc +++ b/src/JsonMapFileParser.cc @@ -3,6 +3,8 @@ #include "JsonMapFileParser.h" +#include "JsonExtensions.h" + #include #include @@ -113,6 +115,31 @@ namespace ChimeraTK::detail { size_t numberOfElements{1}; size_t bytesPerElement{0}; + struct DoubleBufferingInfo { + struct Address { + AddressType type{AddressType::DMA}; + size_t channel{0}; + HexValue offset{0}; + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(Address, type, channel, offset) + }; + + Address secondaryBufferAddress; + std::string enableRegister; + std::string readBufferRegister; + size_t index{0}; + + void fill(NumericAddressedRegisterInfo& info) const { + info.doubleBuffer->offset = secondaryBufferAddress.offset.v; + info.doubleBuffer->enableRegisterPath = enableRegister; + info.doubleBuffer->inactiveBufferRegisterPath = readBufferRegister; + } + + NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT( + DoubleBufferingInfo, secondaryBufferAddress, enableRegister, readBufferRegister, index) + }; + std::optional doubleBuffering; + struct Address { AddressType type{AddressType::IO}; size_t channel{0}; @@ -220,6 +247,15 @@ namespace ChimeraTK::detail { info.interruptId = triggeredByInterrupt; info.channels.emplace_back(0, NumericAddressedRegisterInfo::Type::VOID, 0, 0, false); } + if(!info.doubleBuffer) { + info.doubleBuffer = NumericAddressedRegisterInfo::DoubleBufferInfo{}; + } + if(doubleBuffering.has_value()) { + doubleBuffering->fill(info); + } + else { + info.doubleBuffer.reset(); + } } std::vector children; @@ -235,6 +271,22 @@ namespace ChimeraTK::detail { fill(my, parentName); my.computeDataDescriptor(); catalogue.addRegister(my); + if(doubleBuffering.has_value()) { + // Create the .buf0 register as a copy of the main one + NumericAddressedRegisterInfo buf0Register = my; + buf0Register.pathName = my.pathName + ".BUF0"; + buf0Register.doubleBuffer.reset(); // it's a simple view of the buffer + buf0Register.computeDataDescriptor(); + catalogue.addRegister(buf0Register); + NumericAddressedRegisterInfo buf1Register = my; + buf1Register.pathName = my.pathName + ".BUF1"; + buf1Register.doubleBuffer.reset(); // it's a simple view of the buffer + buf1Register.address = doubleBuffering->secondaryBufferAddress.offset.v; + // buf1Register.bar = doubleBuffering->secondaryBufferAddress.channel + + // (doubleBuffering->secondaryBufferAddress.type == AddressType::DMA ? 13 : 0); + buf1Register.computeDataDescriptor(); + catalogue.addRegister(buf1Register); + } } for(const auto& child : children) { child.addInfos(catalogue, parentName / name); @@ -242,7 +294,8 @@ namespace ChimeraTK::detail { } NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(JsonAddressSpaceEntry, name, engineeringUnit, description, access, - triggeredByInterrupt, numberOfElements, bytesPerElement, address, representation, children, channelTabs) + triggeredByInterrupt, numberOfElements, bytesPerElement, address, representation, children, channelTabs, + doubleBuffering) }; /********************************************************************************************************************/ diff --git a/src/NDDoubleBufferAccessorDecorator.cc b/src/NDDoubleBufferAccessorDecorator.cc new file mode 100644 index 00000000..6aa56cb2 --- /dev/null +++ b/src/NDDoubleBufferAccessorDecorator.cc @@ -0,0 +1,159 @@ +#include "NDDoubleBufferAccessorDecorator.h" +namespace ChimeraTK { + + template + NumericDoubleBufferAccessorDecorator::NumericDoubleBufferAccessorDecorator( + const boost::shared_ptr>& target, + std::optional doubleBufferConfig, + const boost::shared_ptr& backend, + std::shared_ptr controlState) + : NDRegisterAccessorDecorator(target), _doubleBufferInfo(std::move(doubleBufferConfig)), _backend(backend), + _controlState(controlState) { + if(!_doubleBufferInfo) { + throw ChimeraTK::logic_error("DoubleBufferInfo must be provided."); + } + _enableDoubleBufferReg = + backend->getRegisterAccessor(_doubleBufferInfo->enableRegisterPath, 1, _doubleBufferInfo->index, {}); + _currentBufferNumberReg = backend->getRegisterAccessor( + _doubleBufferInfo->inactiveBufferRegisterPath, 1, _doubleBufferInfo->index, {}); + + auto numSamples = _target->getNumberOfSamples(); + auto accessFlags = _target->getAccessModeFlags(); + auto primaryName = _target->getName(); + auto buf0Name = primaryName + ".BUF0"; + auto buf1Name = primaryName + ".BUF1"; + + _target = backend->getRegisterAccessor(buf0Name, numSamples, 0, accessFlags); + _secondBufferReg = backend->getRegisterAccessor(buf1Name, numSamples, 0, accessFlags); + + // enable double buffering?? + // uint32_t enable = 1; + // _enableDoubleBufferReg->accessChannel(0)[0] = enable; + // _enableDoubleBufferReg->write(); + } + + template + void NumericDoubleBufferAccessorDecorator::doPreRead(TransferType type) { + { + std::lock_guard lg(_controlState->mutex); + //_controlState._readerCount.value++; + if(_controlState->readerCount == 0) { + // acquire a lock in firmware (disable buffer swapping) + _enableDoubleBufferReg->accessData(0) = 0; + _enableDoubleBufferReg->write(); + } + ++_controlState->readerCount; + } + // check which buffer is now in use by the firmware + _currentBufferNumberReg->read(); + _currentBuffer = _currentBufferNumberReg->accessData(0); + // if current buffer 1, it means firmware writes now to buffer1, so use target (buffer 0), else use + // _secondBufferReg (buffer 1) + if(_currentBuffer) { + _target->preRead(type); + } + else { + _secondBufferReg->preRead(type); + } + } + + template + void NumericDoubleBufferAccessorDecorator::doReadTransferSynchronously() { + if(_currentBuffer) { + _target->readTransfer(); + } + else { + _secondBufferReg->readTransfer(); + } + } + + template + void NumericDoubleBufferAccessorDecorator::doPostRead(TransferType type, bool hasNewData) { + if(_currentBuffer) { + _target->postRead(type, hasNewData); + } + else { + _secondBufferReg->postRead(type, hasNewData); + } + + { + std::lock_guard lg{_controlState->mutex}; + assert(_controlState->readerCount > 0); + _controlState->readerCount--; + if(_controlState->readerCount == 0) { + /*if(_testUSleep) { + // for testing, check safety of handshake + // FIXME - remove testUSleep feature + _currentBufferNumberReg->read(); + if(_currentBuffer != _currentBufferNumberReg->accessData(0)) { + std::cout << "WARNING: buffer switch happened while reading! Expect corrupted data." << std::endl; + } + }*/ + // release a lock in firmware (enable buffer swapping) + _enableDoubleBufferReg->accessData(0) = 1; + _enableDoubleBufferReg->write(); + } + } + // set version and data validity of this object + this->_versionNumber = {}; + if(_currentBuffer) { + this->_dataValidity = _target->dataValidity(); + } + else { + this->_dataValidity = _secondBufferReg->dataValidity(); + } + + // Note: TransferElement Spec E.6.1 dictates that the version number and data validity needs to be set before this + // check. + if(!hasNewData) { + return; + } + + if(_currentBuffer) { + for(size_t i = 0; i < _target->getNumberOfChannels(); ++i) { + ChimeraTK::NDRegisterAccessorDecorator::buffer_2D[i].swap(_target->accessChannel(i)); + } + } + else { + for(size_t i = 0; i < _secondBufferReg->getNumberOfChannels(); ++i) { + ChimeraTK::NDRegisterAccessorDecorator::buffer_2D[i].swap(_secondBufferReg->accessChannel(i)); + } + } + } + + template + std::vector> NumericDoubleBufferAccessorDecorator< + UserType>::getHardwareAccessingElements() { + // returning only this means the DoubleBufferAccessorDecorator will not be optimized when put into TransferGroup + // optimizing would break our handshake protocol, since it reorders transfers + return {TransferElement::shared_from_this()}; + } + + template + bool NumericDoubleBufferAccessorDecorator::mayReplaceOther( + const boost::shared_ptr& other) const { + // we need this to support merging of accessors using the same double-buffered as target. + // If other is also double-buffered region belonging to the same plugin instance, allow the merge + auto otherDoubleBuffer = boost::dynamic_pointer_cast(other); + if(!otherDoubleBuffer) { + return false; + } + return &(otherDoubleBuffer->_controlState) == &_controlState; + } + + // Explicit template instantiations + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + template class ChimeraTK::NumericDoubleBufferAccessorDecorator; + +} // namespace ChimeraTK diff --git a/src/NumericAddressedBackend.cc b/src/NumericAddressedBackend.cc index bcdd99c8..091b0a36 100644 --- a/src/NumericAddressedBackend.cc +++ b/src/NumericAddressedBackend.cc @@ -7,6 +7,7 @@ #include "async/DomainsContainer.h" #include "Exception.h" #include "MapFileParser.h" +#include "NDDoubleBufferAccessorDecorator.h" #include "NumericAddress.h" #include "NumericAddressedBackendASCIIAccessor.h" #include "NumericAddressedBackendMuxedRegisterAccessor.h" @@ -198,6 +199,16 @@ namespace ChimeraTK { registerPathName, numberOfWords, wordOffsetInRegister, shared_from_this())); } } + // Optionally wrap with double buffer decorator + if(registerInfo.doubleBuffer != std::nullopt) { + const auto& enableRegPath = registerInfo.doubleBuffer->enableRegisterPath; + auto& controlState = _controlStateMap[enableRegPath]; + if(!controlState) { + controlState = std::make_shared(); + } + accessor = boost::make_shared>( + accessor, registerInfo.doubleBuffer, shared_from_this(), controlState); + } accessor->setExceptionBackend(shared_from_this()); return accessor; diff --git a/src/NumericAddressedRegisterCatalogue.cc b/src/NumericAddressedRegisterCatalogue.cc index d3ddc842..920bbb5e 100644 --- a/src/NumericAddressedRegisterCatalogue.cc +++ b/src/NumericAddressedRegisterCatalogue.cc @@ -20,10 +20,11 @@ namespace ChimeraTK { NumericAddressedRegisterInfo::NumericAddressedRegisterInfo(RegisterPath const& pathName_, uint32_t nElements_, uint64_t address_, uint32_t nBytes_, uint64_t bar_, uint32_t width_, int32_t nFractionalBits_, bool signedFlag_, - Access dataAccess_, Type dataType_, std::vector interruptId_) + Access dataAccess_, Type dataType_, std::vector interruptId_, + std::optional doubleBufferInfo_) : pathName(pathName_), nElements(nElements_), elementPitchBits(nElements_ > 0 ? nBytes_ / nElements_ * 8 : 0), bar(bar_), address(address_), registerAccess(dataAccess_), interruptId(std::move(interruptId_)), - channels({{0, dataType_, width_, nFractionalBits_, signedFlag_}}) { + doubleBuffer(std::move(doubleBufferInfo_)), channels({{0, dataType_, width_, nFractionalBits_, signedFlag_}}) { assert(channels.size() == 1); // make sure . and / is treated as similar as possible @@ -45,9 +46,10 @@ namespace ChimeraTK { NumericAddressedRegisterInfo::NumericAddressedRegisterInfo(RegisterPath const& pathName_, uint64_t bar_, uint64_t address_, uint32_t nElements_, uint32_t elementPitchBits_, std::vector channelInfo_, - Access dataAccess_, std::vector interruptId_) + Access dataAccess_, std::vector interruptId_, std::optional doubleBufferInfo_) : pathName(pathName_), nElements(nElements_), elementPitchBits(elementPitchBits_), bar(bar_), address(address_), - registerAccess(dataAccess_), interruptId(std::move(interruptId_)), channels(std::move(channelInfo_)) { + registerAccess(dataAccess_), interruptId(std::move(interruptId_)), doubleBuffer(std::move(doubleBufferInfo_)), + channels(std::move(channelInfo_)) { assert(!channels.empty()); // make sure . and / is treated as similar as possible diff --git a/tests/executables_src/testJsonMapFileParser.cpp b/tests/executables_src/testJsonMapFileParser.cpp index 37f0831d..80bb7fb6 100644 --- a/tests/executables_src/testJsonMapFileParser.cpp +++ b/tests/executables_src/testJsonMapFileParser.cpp @@ -105,6 +105,7 @@ BOOST_AUTO_TEST_CASE(TestGoodMapFileParse) { BOOST_TEST(reg.channels[0].width == 14); BOOST_TEST(reg.channels[0].nFractionalBits == 10); BOOST_TEST(reg.channels[0].signedFlag == true); + BOOST_TEST(!reg.doubleBuffer.has_value()); } { auto reg = regs.getBackendRegister("DAQ.CTRL"); @@ -115,6 +116,10 @@ BOOST_AUTO_TEST_CASE(TestGoodMapFileParse) { BOOST_TEST(reg.address == 0x80000000); BOOST_CHECK(reg.registerAccess == NumericAddressedRegisterInfo::Access::INTERRUPT); BOOST_TEST(reg.interruptId == std::vector({3, 0, 1}), boost::test_tools::per_element()); + BOOST_TEST(reg.doubleBuffer.has_value()); + BOOST_TEST(reg.doubleBuffer->offset == 0x80200000); + BOOST_TEST(reg.doubleBuffer->inactiveBufferRegisterPath == "/DAQ.DOUBLE_BUF.INACTIVE_BUF_ID"); + BOOST_TEST(reg.doubleBuffer->enableRegisterPath == "/DAQ.DOUBLE_BUF.ENA"); BOOST_REQUIRE(reg.channels.size() == 5); @@ -285,7 +290,6 @@ BOOST_AUTO_TEST_CASE(TestInterruptIntegration) { BOOST_TEST(int0.readNonBlocking() == true); BOOST_TEST(int301.readNonBlocking() == true); } - /**********************************************************************************************************************/ BOOST_AUTO_TEST_SUITE_END()