Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions include/DoubleBufferAccessor.h
Comment thread
nshehz marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// 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 <string>

namespace ChimeraTK {

template<typename UserType>
class DoubleBufferAccessor : public NDRegisterAccessor<UserType> {
public:
DoubleBufferAccessor(NumericAddressedRegisterInfo::DoubleBufferInfo doubleBufferConfig,
const boost::shared_ptr<DeviceBackend>& backend, std::shared_ptr<detail::CountedRecursiveMutex> mutex,
const RegisterPath& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags);

void doPreRead(TransferType type) override;

void doReadTransferSynchronously() override;

void doPostRead(TransferType type, bool hasNewData) override;

[[nodiscard]] bool isWriteable() const override { return false; }
[[nodiscard]] bool isReadOnly() const override { return true; }
[[nodiscard]] bool isReadable() const override { return true; };

bool doWriteTransfer(ChimeraTK::VersionNumber /* VersionNumber */) override { return false; }

void doPreWrite(TransferType, VersionNumber) override {
throw ChimeraTK::logic_error("DoubleBufferAccessor: 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<boost::shared_ptr<TransferElement>> getHardwareAccessingElements() override;

std::list<boost::shared_ptr<TransferElement>> getInternalElements() override { return {}; }

void replaceTransferElement(boost::shared_ptr<ChimeraTK::TransferElement> /* newElement */) override {}
[[nodiscard]] bool mayReplaceOther(const boost::shared_ptr<TransferElement const>& other) const override;

protected:
using ChimeraTK::NDRegisterAccessor<UserType>::buffer_2D;
NumericAddressedRegisterInfo::DoubleBufferInfo _doubleBufferInfo;
boost::shared_ptr<DeviceBackend> _backend;
std::shared_ptr<detail::CountedRecursiveMutex> _mutex;
std::unique_lock<detail::CountedRecursiveMutex> _transferLock;
boost::shared_ptr<NDRegisterAccessor<UserType>> _buffer0;
boost::shared_ptr<NDRegisterAccessor<UserType>> _buffer1;
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<uint32_t>> _enableDoubleBufferReg;
boost::shared_ptr<ChimeraTK::NDRegisterAccessor<uint32_t>> _currentBufferNumberReg;
uint32_t _currentBuffer{0};
Comment thread
nshehz marked this conversation as resolved.
};

DECLARE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(DoubleBufferAccessor);
} // namespace ChimeraTK
32 changes: 32 additions & 0 deletions include/JsonExtensions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once

#include <nlohmann/json.hpp>

#include <optional>

namespace nlohmann {

template<typename T>
struct adl_serializer<std::optional<T>> {
static void to_json(json& j, const std::optional<T>& opt) {
if(opt.has_value()) {
j = *opt;
}
else {
j = nullptr;
}
}

static void from_json(const json& j, std::optional<T>& opt) {
if(j.is_null()) {
opt = std::nullopt;
}
else {
opt = j.get<T>();
}
}
};

} // namespace nlohmann
3 changes: 3 additions & 0 deletions include/NumericAddressedBackend.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#pragma once

#include "async/DomainImpl.h"
#include "CountedRecursiveMutex.h"
#include "DeviceBackendImpl.h"
#include "NumericAddressedRegisterCatalogue.h"

Expand Down Expand Up @@ -178,6 +179,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<std::string, std::shared_ptr<detail::CountedRecursiveMutex>> _doubleBufferMutexMap;
};

/********************************************************************************************************************/
Expand Down
14 changes: 11 additions & 3 deletions include/NumericAddressedRegisterCatalogue.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,21 +50,29 @@ namespace ChimeraTK {
[[nodiscard]] DataType getRawType() const; /**< Return raw type matching the given width */
};

struct DoubleBufferInfo {
uint64_t address; /**< Secondary buffer address */
RegisterPath enableRegisterPath; /**< double buffer enable */
RegisterPath inactiveBufferRegisterPath; /**< inactive register (BUF0/BUF1) */
uint32_t index; /**< Index in enable register */
};

/**
* Constructor to set all data members for scalar/1D registers. They all have default values, so this also acts as
* default constructor.
*/
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<size_t> interruptId_ = {});
Type dataType_ = Type::FIXED_POINT, std::vector<size_t> interruptId_ = {},
std::optional<DoubleBufferInfo> 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> channelInfo_, Access dataAccess_,
std::vector<size_t> interruptId_);
std::vector<size_t> interruptId_, std::optional<DoubleBufferInfo> doubleBuffer_ = std::nullopt);

NumericAddressedRegisterInfo(const NumericAddressedRegisterInfo&) = default;

Expand Down Expand Up @@ -115,7 +123,7 @@ namespace ChimeraTK {

Access registerAccess; /**< Data access direction: Read, write, read and write or interrupt */
std::vector<size_t> interruptId;

std::optional<DoubleBufferInfo> doubleBuffer;
/** Define per-channel information (bit interpretation etc.), 1D/scalars have exactly one entry. */
std::vector<ChannelInfo> channels;

Expand Down
131 changes: 131 additions & 0 deletions src/DoubleBufferAccessor.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// SPDX-FileCopyrightText: Deutsches Elektronen-Synchrotron DESY, MSK, ChimeraTK Project <chimeratk-support@desy.de>
// SPDX-License-Identifier: LGPL-3.0-or-later

#include "DoubleBufferAccessor.h"
namespace ChimeraTK {

template<typename UserType>
DoubleBufferAccessor<UserType>::DoubleBufferAccessor(
NumericAddressedRegisterInfo::DoubleBufferInfo doubleBufferConfig,
const boost::shared_ptr<DeviceBackend>& backend, std::shared_ptr<detail::CountedRecursiveMutex> mutex,
const RegisterPath& registerPathName, size_t numberOfWords, size_t wordOffsetInRegister, AccessModeFlags flags)
: NDRegisterAccessor<UserType>(registerPathName, flags), _doubleBufferInfo(std::move(doubleBufferConfig)),
_backend(boost::dynamic_pointer_cast<NumericAddressedBackend>(backend)), _mutex(std::move(mutex)),
_transferLock(*_mutex, std::defer_lock) {
_enableDoubleBufferReg =
backend->getRegisterAccessor<uint32_t>(_doubleBufferInfo.enableRegisterPath, 1, _doubleBufferInfo.index, {});
_currentBufferNumberReg = backend->getRegisterAccessor<uint32_t>(
_doubleBufferInfo.inactiveBufferRegisterPath, 1, _doubleBufferInfo.index, {});

auto buf0Name = registerPathName + ".BUF0";
auto buf1Name = registerPathName + ".BUF1";

_buffer0 = backend->getRegisterAccessor<UserType>(buf0Name, numberOfWords, wordOffsetInRegister, flags);
_buffer1 = backend->getRegisterAccessor<UserType>(buf1Name, numberOfWords, wordOffsetInRegister, flags);
size_t nChannels = _buffer0->getNumberOfChannels();
// size_t nSamples = _buffer0->getNumberOfSamples();

this->buffer_2D.resize(nChannels);
for(size_t i = 0; i < nChannels; ++i) {
buffer_2D[i].resize(numberOfWords);
}

{
std::lock_guard<detail::CountedRecursiveMutex> lg(*_mutex);
if(_mutex->useCount() == 1) {
_enableDoubleBufferReg->accessChannel(0)[0] = 1;
_enableDoubleBufferReg->write();
}
}
}

template<typename UserType>
void DoubleBufferAccessor<UserType>::doPreRead(TransferType type) {
// Acquire lock for full transfer lifecycle
_transferLock.lock(); // blocks other threads
// acquire a lock in firmware (disable buffer swapping)
if(_mutex->useCount() == 1) {
_enableDoubleBufferReg->accessData(0) = 0;
_enableDoubleBufferReg->write();
}
// 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 == 1) {
_buffer0->preRead(type);
}
else {
_buffer1->preRead(type);
}
}

template<typename UserType>
void DoubleBufferAccessor<UserType>::doReadTransferSynchronously() {
if(_currentBuffer == 1) {
_buffer0->readTransfer();
}
else {
_buffer1->readTransfer();
}
}

template<typename UserType>
void DoubleBufferAccessor<UserType>::doPostRead(TransferType type, bool hasNewData) {
auto unlocker = cppext::finally([&] { _transferLock.unlock(); });
if(_currentBuffer == 1) {
_buffer0->postRead(type, hasNewData);
}
else {
_buffer1->postRead(type, hasNewData);
}

// release a lock in firmware (enable buffer swapping)
if(_mutex->useCount() == 1) {
_enableDoubleBufferReg->accessData(0) = 1;
_enableDoubleBufferReg->write();
}
// set version and data validity of this object
this->_versionNumber = {};
if(_currentBuffer == 1) {
this->_dataValidity = _buffer0->dataValidity();
}
else {
this->_dataValidity = _buffer1->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;
}

// Swap buffer_2D if new data
if(hasNewData) {
auto& reg = (_currentBuffer == 1) ? _buffer0 : _buffer1;
for(size_t i = 0; i < reg->getNumberOfChannels(); ++i) {
buffer_2D[i].swap(reg->accessChannel(i));
}
}
}

template<typename UserType>
std::vector<boost::shared_ptr<TransferElement>> DoubleBufferAccessor<UserType>::getHardwareAccessingElements() {
// returning only this means the DoubleBufferAccessor will not be optimized when put into TransferGroup
// optimizing would break our handshake protocol, since it reorders transfers
return {TransferElement::shared_from_this()};
}

template<typename UserType>
bool DoubleBufferAccessor<UserType>::mayReplaceOther(const boost::shared_ptr<const TransferElement>& other) const {
auto otherDoubleBuffer = boost::dynamic_pointer_cast<const DoubleBufferAccessor<UserType>>(other);
if(!otherDoubleBuffer || otherDoubleBuffer.get() == this) {
return false;
}
return (_buffer0->mayReplaceOther(otherDoubleBuffer->_buffer0));
Comment thread
nshehz marked this conversation as resolved.
}

INSTANTIATE_TEMPLATE_FOR_CHIMERATK_USER_TYPES(DoubleBufferAccessor);

} // namespace ChimeraTK
55 changes: 54 additions & 1 deletion src/JsonMapFileParser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

#include "JsonMapFileParser.h"

#include "JsonExtensions.h"

#include <nlohmann/json.hpp>

#include <boost/algorithm/string.hpp>
Expand Down Expand Up @@ -113,6 +115,31 @@ namespace ChimeraTK::detail {
size_t numberOfElements{1};
size_t bytesPerElement{0};

struct DoubleBufferingInfo {
struct SecondAddress {
AddressType type{AddressType::DMA};
Comment thread
killenb marked this conversation as resolved.
size_t channel{0};
HexValue offset{0};
Comment thread
killenb marked this conversation as resolved.

NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(SecondAddress, type, channel, offset)
};

SecondAddress secondBufferAddress;
std::string enableRegister;
std::string readBufferRegister;
size_t index{0};

void fill(NumericAddressedRegisterInfo& info) const {
info.doubleBuffer->address = secondBufferAddress.offset.v;
info.doubleBuffer->enableRegisterPath = enableRegister;
info.doubleBuffer->inactiveBufferRegisterPath = readBufferRegister;
}

NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(
DoubleBufferingInfo, secondBufferAddress, enableRegister, readBufferRegister, index)
};
std::optional<DoubleBufferingInfo> doubleBuffering;

struct Address {
AddressType type{AddressType::IO};
size_t channel{0};
Expand Down Expand Up @@ -220,6 +247,13 @@ namespace ChimeraTK::detail {
info.interruptId = triggeredByInterrupt;
info.channels.emplace_back(0, NumericAddressedRegisterInfo::Type::VOID, 0, 0, false);
}
if(doubleBuffering) {
info.doubleBuffer.emplace();
doubleBuffering->fill(info);
}
else {
info.doubleBuffer.reset();
}
}

std::vector<JsonAddressSpaceEntry> children;
Expand All @@ -235,14 +269,33 @@ 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.registerAccess = NumericAddressedRegisterInfo::Access::READ_ONLY;
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->secondBufferAddress.offset.v;
buf1Register.registerAccess = NumericAddressedRegisterInfo::Access::READ_ONLY;
// buf1Register.bar = doubleBuffering->secondBufferAddress.channel +
// (doubleBuffering->secondaryBufferAddress.type == AddressType::DMA ? 13 : 0);
buf1Register.computeDataDescriptor();
catalogue.addRegister(buf1Register);
}
}
for(const auto& child : children) {
child.addInfos(catalogue, parentName / name);
}
}

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)
};

/********************************************************************************************************************/
Expand Down
Loading