From 1bd439fd3c3732eb3448276b2e634dd07fa9b490 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Sun, 10 Mar 2024 13:45:59 -0500 Subject: [PATCH 01/12] ChannelMapping: added retrieval of more PMT information Digitizer label is already present; LVDS and adder connector information is going to be added to the databases soon. --- .../ChannelMapping/ChannelMapDumper.cxx | 18 ++++-- .../ChannelMapping/ChannelMapPostGres.cxx | 56 +++++++++++++++---- .../ChannelMapping/ChannelMapSQLite.cxx | 38 ++++++++++--- .../ICARUSChannelMapDataTypes.h | 26 +++++++++ .../Decode/ChannelMapping/IChannelMapping.h | 54 ++++++++++++++++++ 5 files changed, 170 insertions(+), 22 deletions(-) diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapDumper.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapDumper.cxx index 58b4c063a..396268ddf 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapDumper.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapDumper.cxx @@ -168,12 +168,22 @@ void dumpPMTmapping(icarusDB::IICARUSChannelMapProvider const& mapping) { << " includes " << digitizerChannels.size() << " LArSoft channels between " << digitizerChannels.front().channelID << " and " << digitizerChannels.back().channelID - << " [board channel index in brackets]:"; - Pager pager{ 8 }; + << " [board channel info in brackets]:"; + Pager pager{ 3 }; for(auto const & chInfo: digitizerChannels) { if (pager.nextHasNewLine()) log << "\n "; - log << " " << std::setw(3) << chInfo.channelID - << " [" << std::setw(3) << chInfo.digitizerChannelNo << "]"; + log << " " << std::setw(3) << chInfo.channelID + << " [" << chInfo.digitizerLabel << "@" + << std::setfill('0') << std::setw(2) << chInfo.digitizerChannelNo; + if (chInfo.hasLVDSinfo()) { + log << ", LVDS:" << chInfo.LVDSconnector << "-" + << std::setw(2) << chInfo.LVDSbit; + } + if (chInfo.hasAdderInfo()) { + log << ", adder:" << chInfo.adderConnector << "-" + << std::setw(2) << chInfo.adderBit; + } + log << "]"; } // for channel } // for fragment diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx index a89b1ec9a..70fc2151f 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx @@ -1,7 +1,7 @@ /** * @file icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx * @brief Interface with ICARUS channel mapping PostGres database. - * @author T. Usher (factorised by Gianluca Petrillo, petrillo@slac.stanford.edu) + * @author T. Usher (factorised by G. Petrillo, petrillo@slac.stanford.edu) * @see icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h */ @@ -415,11 +415,13 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap // input is C-strings; we force the other term of comparison to C++ strings using namespace std::string_literals; auto const [ - ChannelIDcolumn, LaserChannelColumn, DigitizerColumn, - DigitizerChannelNoColumn, FragmentIDcolumn + LaserChannelColumn, DigitizerChannelColumn, ChannelIDcolumn, + FragmentIDcolumn, DigitizerLabelColumn, LVDSconnectorColumn, + AdderConnectorColumn ]= details::WDAPositionFinder{ getTuple(dataset, 0) }( - "channel_id"s, "light_fiber_label"s, "digitizer_label"s, - "digitizer_ch_number"s, "fragment_id"s + "light_fiber_label"s, "digitizer_ch_number"s, "channel_id"s, + "fragment_id"s, "digitizer_label"s, "FPGA_connector_DIO"s, + "adder_connector_DIO"s ); // Ok, now we can start extracting the information @@ -432,16 +434,18 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap int error = 0; - // nice... and currently unused - Expected const digitizerLabel = getStringFromTuple(tuple, DigitizerColumn); + PMTChannelInfo_t chInfo; + auto const nFields = static_cast(getNfields(tuple)); + + // digitizer label + Expected const digitizerLabel + = getStringFromTuple(tuple, DigitizerLabelColumn); if (!digitizerLabel) { throw myException() << "Error (code: " << digitizerLabel.code() << " on row " << row << ") retrieving PMT digitizer from channel mapping database\n"; } - - - PMTChannelInfo_t chInfo; + chInfo.digitizerLabel = digitizerLabel; // fragment id unsigned long const fragmentID @@ -453,7 +457,7 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap // digitizer channel number chInfo.digitizerChannelNo - = getLongValue(tuple, DigitizerChannelNoColumn, &error); + = getLongValue(tuple, DigitizerChannelColumn, &error); if (error) { throw myException() << "Error (code: " << error << " on row " << row << ") reading PMT readout board channel number\n"; @@ -475,6 +479,36 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap // will throw on error: chInfo.laserChannelNo = std::stol(laserChannelLabel.value().substr(2)); + // LVDS connector and bit + if (LVDSconnectorColumn < nFields) { + Expected LVDSconnectorLabel + = getStringFromTuple(tuple, LVDSconnectorColumn); + if (!LVDSconnectorLabel) { + throw myException() << "Error (code: " << LVDSconnectorLabel.code() + << " on row " << row + << ") retrieving LVDS connector from channel mapping database\n"; + } + auto const [ LVDSconnector, LVDSbit ] + = splitIntegers<2, unsigned short int>(LVDSconnectorLabel, "-"); + chInfo.LVDSconnector = LVDSconnector; + chInfo.LVDSbit = LVDSbit; + } + + // adder connector and bit + if (AdderConnectorColumn < nFields) { + Expected adderConnectorLabel + = getStringFromTuple(tuple, AdderConnectorColumn); + if (!adderConnectorLabel) { + throw myException() << "Error (code: " << adderConnectorLabel.code() + << " on row " << row + << ") retrieving adder connector from channel mapping database\n"; + } + auto const [ adderConnector, adderBit ] + = splitIntegers<2, unsigned short int>(adderConnectorLabel, "-"); + chInfo.adderConnector = adderConnector; + chInfo.adderBit = adderBit; + } + // fill the map fragmentToDigitizerChannelMap[fragmentID].push_back(std::move(chInfo)); diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx index 355e9e66f..4ccad3a3a 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx @@ -450,11 +450,13 @@ int icarusDB::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback // input is C-strings; we force the other term of comparison to C++ strings using namespace std::string_literals; auto const [ - LaserChannelColumn, DigitizerChannelColumn, ChannelIDcolumn, - FragmentIDcolumn + LaserChannelColumn, DigitizerChannelColumn, ChannelIDcolumn, + FragmentIDcolumn, DigitizerLabelColumn, LVDSconnectorColumn, + AdderConnectorColumn ] = icarus::ns::util::PositionFinder{ gsl::span{azColName, (unsigned) argc} }( - "light_fiber_label"s, "digitizer_ch_number"s, "channel_id"s, - "fragment_id"s + "light_fiber_label"s, "digitizer_ch_number"s, "channel_id"s, + "fragment_id"s, "digitizer_label"s, "FPGA_connector_DIO"s, + "adder_connector_DIO"s ); auto& fragmentToDigitizerChannelMap @@ -462,7 +464,9 @@ int icarusDB::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback // Start extracting info unsigned int const fragmentID = std::stol(argv[FragmentIDcolumn]); - unsigned int const digitizerChannelNo = std::stol(argv[DigitizerChannelColumn]); + std::string digitizerLabel = argv[DigitizerLabelColumn]; + unsigned int const digitizerChannelNo + = std::stol(argv[DigitizerChannelColumn]); unsigned int const channelID = std::stol(argv[ChannelIDcolumn]); // Read the laser channel; format is `L-`. is int from [1-41] @@ -476,10 +480,30 @@ int icarusDB::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback << "Failed to convert laser channel '" << laserChannelLabel << "' into a channel number for channel " << channelID << "!\n"; } + + // PMT discriminated signal connector and bit + auto const [ LVDSconnector, LVDSbit ] + = (LVDSconnectorColumn < (std::size_t)argc) + ? splitIntegers<2, unsigned short int>(argv[LVDSconnectorColumn], "-") + : std::array + { PMTChannelInfo_t::NoConnector, PMTChannelInfo_t::NoConnectorBit } + ; + + // adder discriminated signal connector and bit + auto const [ adderConnector, adderBit ] + = (AdderConnectorColumn < (std::size_t)argc) + ? splitIntegers<2, unsigned short int>(argv[AdderConnectorColumn], "-") + : std::array + { PMTChannelInfo_t::NoConnector, PMTChannelInfo_t::NoConnectorBit } + ; // Fill the map - fragmentToDigitizerChannelMap[fragmentID].push_back - ({ digitizerChannelNo, channelID, laserChannel }); + fragmentToDigitizerChannelMap[fragmentID].push_back({ // C++20: name members + std::move(digitizerLabel), digitizerChannelNo, + channelID, laserChannel, + LVDSconnector, LVDSbit, + adderConnector, adderBit + }); return 0; } // ...::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback() diff --git a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h index ac0802999..6c9f66c1f 100644 --- a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h +++ b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h @@ -71,6 +71,13 @@ namespace icarusDB { = std::numeric_limits::max(); static constexpr unsigned int NoLaserChannel = std::numeric_limits::max(); + static constexpr unsigned short int NoConnector + = std::numeric_limits::max(); + static constexpr unsigned short int NoConnectorBit + = std::numeric_limits::max(); + + /// Label of the digitizer this channel is on (e.g. `WW-TOP-A`). + std::string digitizerLabel; /// Number of the channel within its digitizer. unsigned int digitizerChannelNo = NoDigitizerChannel; @@ -81,6 +88,25 @@ namespace icarusDB { /// Number of laser channel shining into this PMT. unsigned int laserChannelNo = NoLaserChannel; + /// Number of the connector with the LVDS signal of this channel. + unsigned short int LVDSconnector = NoConnector; + + /// Number of the connector bit carrying the LVDS signal of this channel. + unsigned short int LVDSbit = NoConnectorBit; + + /// Number of the connector with the adder signal including this channel. + unsigned short int adderConnector = NoConnector; + + /// Number of the connector bit carrying the adder signal including this + /// channel. + unsigned short int adderBit = NoConnectorBit; + + /// Returns whether the LVDS connector bit information is available. + constexpr bool hasLVDSinfo() const { return LVDSbit != NoConnectorBit; } + + /// Returns whether the adder connector bit information is available. + constexpr bool hasAdderInfo() const { return adderBit != NoConnectorBit; } + /// Sorting by channel ID. constexpr bool operator< (PMTChannelInfo_t const& other) const noexcept { return channelID < other.channelID; } diff --git a/icaruscode/Decode/ChannelMapping/IChannelMapping.h b/icaruscode/Decode/ChannelMapping/IChannelMapping.h index 2be79235d..afc137c2c 100644 --- a/icaruscode/Decode/ChannelMapping/IChannelMapping.h +++ b/icaruscode/Decode/ChannelMapping/IChannelMapping.h @@ -12,12 +12,16 @@ #include "icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h" // LArSoft libraries +#include "larcorealg/CoreUtils/counter.h" #include "larcorealg/CoreUtils/enumerate.h" // C/C++ standard libraries #include // std::transform() #include #include +#include +#include // std::make_error_code() +#include // std::from_char() #include // std::tolower() @@ -126,11 +130,61 @@ class icarusDB::IChannelMapping { return PlaneNames.size(); } + /// Splits `s` into `N` integers of type `Integer` separated by `sep`. + /// @return an array of `N` integers of type `Integer` extracted from `s` + template + static std::array splitIntegers + (std::string const& s, std::string const& sep); + // --- END ----- Utility functions for the implementation -------------------- }; // icarusDB::IChannelMapping +// ----------------------------------------------------------------------------- +// --- template implementation +// ----------------------------------------------------------------------------- +template +std::array icarusDB::IChannelMapping::splitIntegers + (std::string const& s, std::string const& sep) +{ + std::array values; + + auto convert = [](std::string_view s) + { + Integer value; + std::from_chars_result const res + = std::from_chars(s.cbegin(), s.cend(), value); + if (res.ec != std::errc{}) { + std::error_code ec = std::make_error_code(res.ec); + throw std::runtime_error{ "Failed to convert '" + std::string{ s } + + "' into an integer: " + ec.message() + " (code: " + + std::to_string(ec.value()) + ")." + }; + } + return value; + }; + + std::string_view const sv{ s }; + std::size_t c = 0; // cursor + for (std::size_t const i: util::counter(N - 1)) { + std::size_t sc = sv.find(sep, c); + if (sc == std::string_view::npos) { + throw std::runtime_error{ "Separator '" + std::string{ sep } + + "' not found after number #" + std::to_string(i) + " of string '" + + std::string{ s } + "'" + }; + } + values[i] = convert(sv.substr(c, sc)); + c = sc + sep.length(); + } // for + + values[N - 1] = convert(sv.substr(c)); + + return values; +} // icarusDB::IChannelMapping::splitIntegers() + + // ----------------------------------------------------------------------------- #endif // ICARUSCODE_DECODE_CHANNELMAPPING_ICHANNELMAPPING_H From a7dd8057422df82136476088321ee994e81379b4 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 13 Mar 2024 12:51:07 -0500 Subject: [PATCH 02/12] ChannelMap: fixed PMT assertion --- .../Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc index 3aea619d8..25937dc80 100644 --- a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc +++ b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc @@ -408,7 +408,7 @@ constexpr unsigned int icarusDB::ICARUSChannelMapProviderBase::PMTfrag // protest if this is a fragment not from the PMT; // but make an exception for old PMT fragment IDs (legacy) - assert(((fragmentID & ~0xFF) == 0x00) || ((fragmentID & ~0xFF) == 0x20)); + assert(((fragmentID & ~0xFF) == 0x0000) || ((fragmentID & ~0xFF) == 0x2000)); return fragmentID & 0xFF; From f10ebeebad13e7ea1b2448a885a6ec7c329264f9 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 15 Mar 2024 19:39:16 -0500 Subject: [PATCH 03/12] util::Expected moved to its own header and closer to C++23 --- .../ChannelMapping/ChannelMapPostGres.cxx | 48 ++++++----- .../ChannelMapping/ChannelMapPostGres.h | 32 +------ icaruscode/Utilities/Expected.h | 84 +++++++++++++++++++ 3 files changed, 112 insertions(+), 52 deletions(-) create mode 100644 icaruscode/Utilities/Expected.h diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx index 70fc2151f..80dbb47bc 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx @@ -230,15 +230,15 @@ int icarusDB::ChannelMapPostGres::BuildTPCFragmentIDToReadoutIDMap // Note that the fragment ID is stored in the database as a string which // reads as a hex number, meaning we have to read back as a string and // decode to get the numerical value - Expected const fragmentIDString + util::Expected const fragmentIDString = getStringFromTuple(tuple, FragmentIDcolumn); if (!fragmentIDString) { - throw myException() << "Error (code: " << fragmentIDString.code() + throw myException() << "Error (code: " << fragmentIDString.error() << " on row " << row << ") retrieving TPC fragment ID from channel mapping database\n"; } - unsigned int const fragmentID = std::stol(fragmentIDString, nullptr, 16); + unsigned int const fragmentID = std::stol(*fragmentIDString, nullptr, 16); if (!(fragmentID & tpcIdentifier)) continue; if (fragmentBoardMap.find(fragmentID) == fragmentBoardMap.end()) { @@ -251,16 +251,17 @@ int icarusDB::ChannelMapPostGres::BuildTPCFragmentIDToReadoutIDMap } // build the flange name - Expected const chimneyNoString = getStringFromTuple(tuple, ChimneyNoColumn); + util::Expected const chimneyNoString + = getStringFromTuple(tuple, ChimneyNoColumn); if (!chimneyNoString) { - throw myException() << "Error (code: " << chimneyNoString.code() + throw myException() << "Error (code: " << chimneyNoString.error() << " on row " << row << ") retrieving chimney number from channel mapping database\n"; } - Expected const TPCIDstring = getStringFromTuple(tuple, TPCIDcolumn); + util::Expected const TPCIDstring = getStringFromTuple(tuple, TPCIDcolumn); if (!TPCIDstring) { - throw myException() << "Error (code: " << TPCIDstring.code() + throw myException() << "Error (code: " << TPCIDstring.error() << " on row " << row << ") retrieving TPC tag from channel mapping database\n"; } @@ -358,14 +359,14 @@ int icarusDB::ChannelMapPostGres::BuildTPCReadoutBoardToChannelMap } // Recover the plane identifier - Expected const fragmentBuffer + util::Expected const fragmentBuffer = getStringFromTuple(tuple, PlaneIdentifierColumn); if (!fragmentBuffer) { - throw myException() << "Error (code: " << fragmentBuffer.code() + throw myException() << "Error (code: " << fragmentBuffer.error() << " on row " << row << ") reading plane type\n"; } // Make sure lower case... (sigh...) - unsigned int const plane = TPCplaneIdentifierToPlane(fragmentBuffer); + unsigned int const plane = TPCplaneIdentifierToPlane(*fragmentBuffer); if (plane >= 3) { mf::LogError{ "ChannelMapSQLite" } << "YIKES!!! Plane is " << plane << " for channel " << channelID << " with type " @@ -438,14 +439,14 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap auto const nFields = static_cast(getNfields(tuple)); // digitizer label - Expected const digitizerLabel + util::Expected const digitizerLabel = getStringFromTuple(tuple, DigitizerLabelColumn); if (!digitizerLabel) { - throw myException() << "Error (code: " << digitizerLabel.code() + throw myException() << "Error (code: " << digitizerLabel.error() << " on row " << row << ") retrieving PMT digitizer from channel mapping database\n"; } - chInfo.digitizerLabel = digitizerLabel; + chInfo.digitizerLabel = *digitizerLabel; // fragment id unsigned long const fragmentID @@ -470,9 +471,10 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap << ") reading PMT channel ID\n"; } // laser channel number - Expected laserChannelLabel = getStringFromTuple(tuple, LaserChannelColumn); + util::Expected laserChannelLabel + = getStringFromTuple(tuple, LaserChannelColumn); if (!laserChannelLabel) { - throw myException() << "Error (code: " << laserChannelLabel.code() + throw myException() << "Error (code: " << laserChannelLabel.error() << " on row " << row << ") retrieving PMT laser channel from channel mapping database\n"; } @@ -481,30 +483,30 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap // LVDS connector and bit if (LVDSconnectorColumn < nFields) { - Expected LVDSconnectorLabel + util::Expected LVDSconnectorLabel = getStringFromTuple(tuple, LVDSconnectorColumn); if (!LVDSconnectorLabel) { - throw myException() << "Error (code: " << LVDSconnectorLabel.code() + throw myException() << "Error (code: " << LVDSconnectorLabel.error() << " on row " << row << ") retrieving LVDS connector from channel mapping database\n"; } auto const [ LVDSconnector, LVDSbit ] - = splitIntegers<2, unsigned short int>(LVDSconnectorLabel, "-"); + = splitIntegers<2, unsigned short int>(*LVDSconnectorLabel, "-"); chInfo.LVDSconnector = LVDSconnector; chInfo.LVDSbit = LVDSbit; } // adder connector and bit if (AdderConnectorColumn < nFields) { - Expected adderConnectorLabel + util::Expected adderConnectorLabel = getStringFromTuple(tuple, AdderConnectorColumn); if (!adderConnectorLabel) { - throw myException() << "Error (code: " << adderConnectorLabel.code() + throw myException() << "Error (code: " << adderConnectorLabel.error() << " on row " << row << ") retrieving adder connector from channel mapping database\n"; } auto const [ adderConnector, adderBit ] - = splitIntegers<2, unsigned short int>(adderConnectorLabel, "-"); + = splitIntegers<2, unsigned short int>(*adderConnectorLabel, "-"); chInfo.adderConnector = adderConnector; chInfo.adderBit = adderBit; } @@ -739,8 +741,8 @@ bool icarusDB::ChannelMapPostGres::printDatasetError // ----------------------------------------------------------------------------- template -auto icarusDB::ChannelMapPostGres::getStringFromTuple - (details::WDATuple const& tuple, std::size_t column) -> Expected +util::Expected icarusDB::ChannelMapPostGres::getStringFromTuple + (details::WDATuple const& tuple, std::size_t column) { int error = 0; std::string buffer(BufferSize, '\0'); diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h index 8413b887d..e7f16332e 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h +++ b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h @@ -1,7 +1,7 @@ /** * @file icaruscode/Decode/ChannelMapping/ChannelMapPostGres.h * @brief Interface with ICARUS channel mapping PostGres database. - * @author T. Usher (factorised by Gianluca Petrillo, petrillo@slac.stanford.edu) + * @author T. Usher (factorised by G. Petrillo, petrillo@slac.stanford.edu) * @see icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx */ @@ -12,6 +12,7 @@ // ICARUS libraries #include "icaruscode/Decode/ChannelMapping/IChannelMapping.h" #include "icaruscode/Decode/ChannelMapping/RunPeriods.h" +#include "icaruscode/Utilities/Expected.h" #include "icarusalg/Utilities/mfLoggingClass.h" // framework libraries @@ -133,33 +134,6 @@ class icarusDB::ChannelMapPostGres virtual int BuildSideCRTCalibrationMap(SideCRTChannelToCalibrationMap&) const override; private: - - /// Loosely based on C++-20 `std::expected`. - template - class Expected { - std::variant fValue; - public: - constexpr Expected(E e = E{}): fValue{ std::move(e) } {} - constexpr Expected(T t): fValue{ std::move(t) } {} - - Expected& operator= (E e) { fValue = std::move(e); return *this; } - Expected& operator= (T t) { fValue = std::move(t); return *this; } - template - T& emplace(Args&&... args) - { return fValue.emplace(std::forward(args)...); } - - bool has_value() const { return std::holds_alternative(fValue); } - - E code() const { return std::get(fValue); } - T const& value() const { return std::get(fValue); } - - operator T const& () const { return value(); } - T const& operator*() const { return value(); } - - explicit operator bool() const { return has_value(); } - - }; // Expected - // --- BEGIN --- Configuration parameters ------------------------------------ @@ -225,7 +199,7 @@ class icarusDB::ChannelMapPostGres /// Returns the string on the specified `column` of the `tuple`. /// @return the requested string, or an error code from libwda on error template - static Expected getStringFromTuple + static util::Expected getStringFromTuple (details::WDATuple const& tuple, std::size_t column); /// Returns an exception object pre-approved with our signature. diff --git a/icaruscode/Utilities/Expected.h b/icaruscode/Utilities/Expected.h new file mode 100644 index 000000000..3995812d2 --- /dev/null +++ b/icaruscode/Utilities/Expected.h @@ -0,0 +1,84 @@ +/** + * @file icaruscode/Utilities/Expected.h + * @brief Class providing a value or an error. + * @author petrillo@slac,stanford.edu + * @date March 13, 2024 + * + * This library is header-only. + */ + +#ifndef ICARUSCODE_UTILITIES_EXPECTED_H +#define ICARUSCODE_UTILITIES_EXPECTED_H + +// C/C++ standard libraries +#include +#include // std::move(), std::forward() + + +// ----------------------------------------------------------------------------- +namespace util { template class Expected; } + +/** + * @brief Class holding a value or an error. + * @tparam T type of the value + * @tparam E (default: `int`) type of the error + * + * This class is loosely based on C++-23 `std::expected`. When C++23 is + * available, this class should be removed. + * The main difference is the default value of the error type `E`, which is not + * present at all in the standard (so the upgrade path would be to define + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * namespace util { + * template + * using Expected = std::expected; + * } + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +template +class util::Expected { + + std::variant fValue; ///< The stored value or error. + + public: + + /// Constructor: starts with a default-constructed error value. + constexpr Expected(E e = E{}): fValue{ std::move(e) } {} + + /// Constructor: starts with a copy of the value `t`. + constexpr Expected(T t): fValue{ std::move(t) } {} + + Expected& operator= (E e) { fValue = std::move(e); return *this; } + Expected& operator= (T t) { fValue = std::move(t); return *this; } + + /// Constructs the expected _value_ in place. + template + T& emplace(Args&&... args) + { return fValue.emplace(std::forward(args)...); } + + /// Returns whether a value is stored (as opposed to an error). + bool has_value() const { return std::holds_alternative(fValue); } + + /// Returns the error (undefined behaviour when storing a value). + E error() const { return std::get(fValue); } + + /// Returns the value (undefined behaviour when storing an error). + T const& value() const { return std::get(fValue); } + + /// Returns a pointer to the value (`nullptr` when storing an error). + T const* valuePtr() const { return has_value()? &value(): nullptr; } + + /// Implicit conversion to value (undefined behaviour if storing an error). + T const& operator*() const { return value(); } + + /// Implicit conversion to value (undefined behaviour if storing an error). + T const* operator-> () const { return valuePtr(); } + + /// Returns whether a value is stored (as opposed to an error). + explicit operator bool() const { return has_value(); } + +}; // util::Expected + + +// ----------------------------------------------------------------------------- + +#endif // ICARUSCODE_UTILITIES_EXPECTED_H From af2555dbbd82cc99bd1a3bd97ad78660c8f542c9 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 15 Mar 2024 19:40:31 -0500 Subject: [PATCH 04/12] IICARUSChannelMap interface to satisfy service provider requirements It can't still be used with `lar::providerFrom` because it's movable. --- icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h b/icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h index 4735c2098..338896d61 100644 --- a/icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h +++ b/icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h @@ -66,6 +66,14 @@ namespace icarusDB { class IICARUSChannelMap; }; class icarusDB::IICARUSChannelMap : virtual public icarusDB::IICARUSChannelMapProvider { + public: + + /// Type of the service provider. + using provider_type = icarusDB::IICARUSChannelMapProvider; + + /// Returns the service provider. + provider_type const* provider() const { return this; } + }; From 06b0b9ff848b4281d80de945b033ab633fd7ff44 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 15 Mar 2024 19:43:03 -0500 Subject: [PATCH 05/12] IICARUSChannelMapping providers support cache tracking This is a new feature that allows callers to know if the content provided by the service has changed from the last check or not. It will be used by a coming trigger decoder. --- .../ICARUSChannelMapProviderBase.h | 15 + .../ICARUSChannelMapProviderBase.tcc | 12 +- .../IICARUSChannelMapProvider.h | 8 +- icaruscode/Utilities/CacheCounter.h | 283 ++++++++++++++++++ 4 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 icaruscode/Utilities/CacheCounter.h diff --git a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.h b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.h index 66f511e0e..080308d8e 100644 --- a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.h +++ b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.h @@ -31,6 +31,21 @@ namespace icarusDB { * @tparam ChMapAlg type of channel mapping helper to be used * * + * Caches + * ------- + * + * This implementation relies on a database backend reading the full information + * from the database and caching the result, every time a new run period is + * requested. To help users who in turn cache information from this object to + * track whether _their_ caches are invalidated by such occurrences, this object + * uses the `util::CacheCounter` facility to version every cache. + * Users will want to use the `util::CacheGuard` tool to monitor the cache. + * Three caches are tracked, with tags `"TPC"`, `"PMT"` and `"CRT"`, plus + * the overall cache tracking (tagged with an empty name `""`). + * At the moment of writing, the three caches are actually updated all at the + * same times. + * + * * Configuration parameters * ========================= * diff --git a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc index 25937dc80..01397887d 100644 --- a/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc +++ b/icaruscode/Decode/ChannelMapping/ICARUSChannelMapProviderBase.tcc @@ -29,6 +29,7 @@ icarusDB::ICARUSChannelMapProviderBase::ICARUSChannelMapProviderBase , fDiagnosticOutput { config.DiagnosticOutput() } , fChannelMappingAlg { config.ChannelMappingTool() } { + addCacheTags({ "TPC", "PMT", "CRT" }); // all caches updated at the same time } @@ -265,13 +266,16 @@ void icarusDB::ICARUSChannelMapProviderBase::readFromDatabase() { mfLogInfo() << "Building the channel mapping"; + updateCacheID("TPC"); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // TPC fragment-based mapping cet::cpu_timer theClockFragmentIDs; theClockFragmentIDs.start(); fTPCFragmentToReadoutMap.clear(); - if (fChannelMappingAlg.BuildTPCFragmentIDToReadoutIDMap(fTPCFragmentToReadoutMap)) - { + if ( + fChannelMappingAlg.BuildTPCFragmentIDToReadoutIDMap(fTPCFragmentToReadoutMap) + ) { throw myException() << "Cannot recover the TPC fragment ID channel map from the database.\n"; } @@ -311,6 +315,8 @@ void icarusDB::ICARUSChannelMapProviderBase::readFromDatabase() { << ", Readout IDs time: " << readoutIDsTime; + updateCacheID("PMT"); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // PMT channel mapping fPMTFragmentToDigitizerMap.clear(); @@ -331,6 +337,8 @@ void icarusDB::ICARUSChannelMapProviderBase::readFromDatabase() { } + updateCacheID("CRT"); + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Side CRT channel mapping fCRTChannelIDToHWtoSimMacAddressPairMap.clear(); diff --git a/icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h b/icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h index 9546a2cb9..cdc37113d 100644 --- a/icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h +++ b/icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h @@ -10,6 +10,7 @@ // ICARUS libraries #include "icaruscode/Decode/ChannelMapping/RunPeriods.h" #include "icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h" +#include "icaruscode/Utilities/CacheCounter.h" // C/C++ standard libraries #include @@ -39,9 +40,12 @@ namespace icarusDB { class IICARUSChannelMapProvider; } * expected that implementations cache results in their final form and provide * direct access to that cache. * - * + * The interface includes cache tracking support via `util::CacheCounter`. + * Implementations are expected to maintain the tracking of the cache with the + * default (empty) name; by default the cache will be always considered + * outdated. */ -class icarusDB::IICARUSChannelMapProvider { +class icarusDB::IICARUSChannelMapProvider: public util::CacheCounter { public: diff --git a/icaruscode/Utilities/CacheCounter.h b/icaruscode/Utilities/CacheCounter.h new file mode 100644 index 000000000..575f4a9aa --- /dev/null +++ b/icaruscode/Utilities/CacheCounter.h @@ -0,0 +1,283 @@ +/** + * @file icaruscode/Utilities/CacheCounter.h + * @brief Simple utility to track cache changes. + * @author Gianluca Petrillo (petrillo@slac.standord.edu) + * @date March 11, 2024 + * + * This library is header only. + */ + +#ifndef ICARUSCODE_UTILITIES_CACHECOUNTER_H +#define ICARUSCODE_UTILITIES_CACHECOUNTER_H + +// C/C++ standard libraries +#include +#include +#include +#include +#include // std::logic_error + + +// ----------------------------------------------------------------------------- +namespace util { + class CacheCounter; + class CacheGuard; +} + +// ----------------------------------------------------------------------------- +/** + * @brief Counter of cache versions. + * @see `util::CacheGuard` + * + * This simple class contains an index of cache IDs. + * A cache ID is a number that represents the status of a cache. + * + * Multiple caches are supported by a single object instance. + * Each cache is represented by a "tag", or name. The cache with empty name is + * special in that it represent the whole set of caches: when updating it, all + * the other cache IDs are also updated. + * + * Note that the special ID value `NoCache` represents the idea that the cache + * is never up to date and always needs to be refreshed (functionally equivalent + * to not having any cache). + * + * See `util::CacheGuard` for a usage example. + */ +class util::CacheCounter { + + public: + + using CacheID_t = unsigned long; ///< Type of cache identifier. + using CacheTag_t = std::string; ///< Type of cache name. + + /// ID specifying that cache is not present yet. + static constexpr CacheID_t NoCache = 0; + + /// Constructor: prepares the ID of the default cache tag. + CacheCounter() { fCacheID.emplace("", NoCache); } + + /// Constructor: prepares the ID of the caches with the specified tags. + CacheCounter(std::initializer_list tags) + : CacheCounter{} + { addCacheTags(std::move(tags)); } + + /// Adds tags. The ID of existing ones are not changed. + void addCacheTags(std::initializer_list tags) + { for (CacheTag_t const& tag: tags) fCacheID.emplace(tag, NoCache); } + + /// Returns the current ID of the cache with the specified tag. + CacheID_t cacheID(CacheTag_t const& tag) const { return fCacheID.at(tag); } + + /// Returns whether the specified tag is tracked. + bool hasTag(CacheTag_t const& tag) const { return fCacheID.count(tag) > 0; } + + /// Returns whether the cache `tag` was updated since it has `sinceID` ID. + bool wasCacheUpdatedSince(CacheTag_t const& tag, CacheID_t sinceID) const + { return cacheID(tag) > sinceID; } + + /// Returns whether the default cache was updated since it has `sinceID` ID. + bool wasCacheUpdatedSince(CacheID_t sinceID) const + { return wasCacheUpdatedSince({}, sinceID); } + + + protected: + + /// ID of the current caches. + mutable std::map> fCacheID; + + /// Increments the counter of the specified cache tag. + /// The default (empty) tag increments all. + CacheID_t updateCacheID(CacheTag_t const& cacheTag = "") const + { + if (cacheTag.empty()) { + for (auto& ID: fCacheID) if (!ID.first.empty()) ++(ID.second); + } + else ++fCacheID.at(""); // always update the default cache + return ++fCacheID.at(cacheTag); // return only the ID from the argument + } + +}; // util::CacheCounter + + +// ----------------------------------------------------------------------------- +/** + * @brief Tracks the status of a cache. + * @see `util::CacheCounter` + * + * This class tracks the status of a cache, represented by an integer (ID). + * The cache is supposed to update (increment) that ID value each time it + * updates the cache content itself. This object tracks that ID and reports + * whether it has changed since the last time. + * + * There are two elements of the interface: + * * `update()` unconditionally acquires the current ID of the cache; it also + * returns whether it's a new ID (like `wasUpdated()`). + * * `wasUpdated()` compares the current cache ID to the one acquired by the + * last `update()` call, and returns whether the current cache is newer. + * + * While the cache ID can be any number, and the interface allows to directly + * provide the cache ID, nonetheless this class has a special relationship with + * `util::CacheCounter`: it can point to a specific tag name of any cache, + * or it can point to a specific cache and tag, with a simpler interface. + * If a `CacheGuard` object needs to track a specific cache or cache tag, that + * must be specified at construction time. + * + * Note that the object always starts with no update. + * + * Also note that a cache ID of `util::CacheCounter::NoCache` is specially + * treated and a cache with that ID will never be considered up to date. + * + * + * Example + * -------- + * + * A database access class performs standard queries reading time-dependent + * data from a database. It caches the result of the query, and it updates + * the cache when a time is requested that is not the one present in the cache. + * This class also derives from `util::CacheCounter` to offer the cache updating + * track capability. + * + * An analysis class uses the data from the database to produce results. + * It stores the small amount of data derived by the database that is needed + * for the computation, and it needs to refetch or recompute that data when + * its local cache is outdated. To do this it has a `util::CacheGuard` data + * member bound to the database access class above. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.cpp} + * class DatabaseQuery: public util::CacheCounter { + * + * bool fetchData() + * { + * // ... + * return true; + * } + * + * void updateCache() { if (fetchData()) updateCacheID(); } + * + * public: + * + * using Data_t = ... ; + * + * Data_t query(); + * + * }; + * + * DatabaseQuery DBquery; + * + * + * class DataUser { + * + * util::CacheGuard fDatabaseQueryCacheGuard; + * DatabaseQuery::Data_t fCachedQueryResult; + * + * DatabaseQuery::Data_t const& queryResults() + * { + * if (fDatabaseQueryCacheGuard.update()) + * fCachedQueryResult = DBquery.query(); + * return fCachedQueryResult; + * } + * + * + * public: + * + * DataUser(): fDatabaseQueryCache{ DBquery } {} + * + * void compute() + * { + * DatabaseQuery::Data_t const& data = queryResults(); + * // ... + * } + * + * }; // DataUser + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * + */ +class util::CacheGuard { + + /// Pointer to the cache to monitor by default. + CacheCounter const* fDefaultCache = nullptr; + + /// Tag of the cache to monitor. + CacheCounter::CacheTag_t fCacheTag; + + /// The ID of the cache at the last check. + CacheCounter::CacheID_t fLastCacheID = CacheCounter::NoCache; + + /// Returns the default cache object, throwing `std::logic_error` if not set. + CacheCounter const& defaultCache() const + { + if (!fDefaultCache) + throw std::logic_error{ "util::CacheGuard default cache was not set." }; + return *fDefaultCache; + } + + /// Changes the default cache. + void chooseCache(CacheCounter const* cache) + { fDefaultCache = cache; fLastCacheID = CacheCounter::NoCache; } + + public: + + /// Constructor: prepares monitoring of cache `tag`. + CacheGuard(CacheCounter::CacheTag_t tag = ""): fCacheTag{ std::move(tag) } {} + + /// Constructor: prepares monitoring of cache `tag` of the specified object. + CacheGuard(CacheCounter const& cache, CacheCounter::CacheTag_t tag = "") + : fDefaultCache{ &cache }, fCacheTag{ std::move(tag) } {} + + /// Changes the default cache. + void setCache(CacheCounter const& cache) { chooseCache(&cache); } + + /// Removes the default cache. + void unsetCache() { chooseCache(nullptr); } + + + /// Returns the ID of the cache at the last update. + CacheCounter::CacheID_t lastUpdateID() const { return fLastCacheID; } + + + /// @{ + + /// Returns whether the cache, which has `cacheID` ID, was updated since the + /// last `update()` call. + bool wasUpdated(CacheCounter::CacheID_t cacheID) const + { return (cacheID != CacheCounter::NoCache) && (cacheID > fLastCacheID); } + + /// Returns whether the `cache` was updated since the last `update()` call. + bool wasUpdated(CacheCounter const& cache) const + { return wasUpdated(cache.cacheID(fCacheTag)); } + + /// Returns whether the default cache was updated since the last `update()` + /// call. + bool wasUpdated() const { return wasUpdated(defaultCache()); } + + /// @} + + + /// @{ + + /// Returns if the cache was outdated, and at the same time updates its ID. + bool update(CacheCounter::CacheID_t cacheID) + { + if (cacheID == CacheCounter::NoCache) return true; // no tracking + if (fLastCacheID == cacheID) return false; + fLastCacheID = cacheID; + return true; + } + + /// Returns if the cache was outdated, and at the same time updates its ID. + bool update(CacheCounter const& cache) + { return update(cache.cacheID(fCacheTag)); } + + /// Returns if the default cache was outdated, and at the same time updates + /// its ID. + bool update() { return update(defaultCache()); } + + /// @} + +}; // util::CacheGuard + + +// ----------------------------------------------------------------------------- + +#endif // ICARUSCODE_UTILITIES_CACHECOUNTER_H From 28bffa5d46872941de5cddb384c187d26130fa33 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 15 Mar 2024 19:46:42 -0500 Subject: [PATCH 06/12] Trigger decoder sanitises the trigger string when dumping it to screen --- .../DecoderTools/TriggerDecoderV3_tool.cc | 34 ++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc index 4953e4cd3..ad15181b7 100644 --- a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc +++ b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc @@ -315,6 +315,9 @@ namespace daq /// Returns the beam type corresponding to the specified trigger `source`. static sim::BeamType_t simGateType(sbn::triggerSource source); + /// Returns `s` with non-printable characters replaced. + static std::string sanitize(std::string const& s); + }; @@ -556,7 +559,7 @@ namespace daq << parsedData << std::endl; if (fDebug) { // this grows tiresome quickly when processing many events - std::cout << "Trigger packet content:\n" << data + std::cout << "Trigger packet content:\n" << sanitize(data) << "\nFull trigger fragment dump:" << sbndaq::dumpFragment(fragment) << std::endl; } @@ -906,6 +909,35 @@ namespace daq } // TriggerDecoderV3::simGateType() + std::string TriggerDecoderV3::sanitize(std::string const& s) { + + static char const HexChar[] = "012345789ABCDEF"; + + std::string ss; + ss.reserve(s.size()); + + for (char c: s) { + if (!std::isprint(static_cast(c)) && (c != '\n')) { + ss += '<'; + switch (c) { + case '\x00': ss += "NUL"; break; + case '\x09': ss += "TAB"; break; + case '\x1E': ss += "ESC"; break; + default: + ss += HexChar[(c >> 4) & 0xF]; + ss += HexChar[c & 0xF]; + } // switch + ss += '>'; + } + else + ss.push_back(c); + + } // for + + return ss; + } // TriggerDecoderV3::sanitize() + + DEFINE_ART_CLASS_TOOL(TriggerDecoderV3) } From ff9612b8934072c48d30696b67dea974cbf1a044 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 15 Mar 2024 19:47:31 -0500 Subject: [PATCH 07/12] Trigger decoder (V3) now uses channel mapping database for decoding bits PMT pair bits are assigned to their proper position via a mapping informed by the PMT channel mapping database (which requires an update). In case the information is not available (old database), an option can be activated to allow for a legacy hard-coded fallback. Note that this legacy mapping is known to be incorrect for Run2. The use of the database is enabled by default. A utility to map LVDS bits and PMT channels is also provided (and used by the decoder). Adder bits are not yet mapped in this way. --- .../Run2/partial/decodeTrigger_icarus.fcl | 2 + icaruscode/Decode/DecoderTools/CMakeLists.txt | 2 + .../DecoderTools/TriggerDecoderV3_tool.cc | 179 ++++++- .../PMT/Trigger/Algorithms/LVDSbitMaps.cxx | 268 +++++++++++ .../PMT/Trigger/Algorithms/LVDSbitMaps.h | 450 ++++++++++++++++++ 5 files changed, 889 insertions(+), 12 deletions(-) create mode 100644 icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx create mode 100644 icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h diff --git a/fcl/reco/Stage0/Run2/partial/decodeTrigger_icarus.fcl b/fcl/reco/Stage0/Run2/partial/decodeTrigger_icarus.fcl index 373d762a5..d041c0023 100644 --- a/fcl/reco/Stage0/Run2/partial/decodeTrigger_icarus.fcl +++ b/fcl/reco/Stage0/Run2/partial/decodeTrigger_icarus.fcl @@ -1,4 +1,5 @@ #include "services_common_icarus.fcl" +#include "channelmapping_icarus.fcl" #include "rootoutput_icarus.fcl" #include "decoderdefs_icarus.fcl" @@ -11,6 +12,7 @@ services: { @table::icarus_geometry_services DetectorClocksService: @local::icarus_detectorclocks + IICARUSChannelMap: @local::icarus_channelmappinggservice # from channelmapping_icarus.fcl } diff --git a/icaruscode/Decode/DecoderTools/CMakeLists.txt b/icaruscode/Decode/DecoderTools/CMakeLists.txt index f0c7bddf6..9d58722ca 100644 --- a/icaruscode/Decode/DecoderTools/CMakeLists.txt +++ b/icaruscode/Decode/DecoderTools/CMakeLists.txt @@ -27,6 +27,8 @@ set( TOOL_LIBRARIES icarus_signal_processing::Detection icarus_signal_processing::Filters icaruscode::TPC_Utilities_SignalShapingICARUSService_service + icaruscode::PMT_Trigger_Algorithms + icaruscode::Decode_ChannelMapping icaruscode::Decode_DecoderTools icaruscode::Decode_DecoderTools_Dumpers icaruscode::Utilities diff --git a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc index ad15181b7..56b19b495 100644 --- a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc +++ b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc @@ -24,6 +24,8 @@ #include "lardata/DetectorInfoServices/DetectorClocksService.h" #include "lardataalg/DetectorInfo/DetectorTimings.h" #include "lardataalg/Utilities/quantities/spacetime.h" // util::quantities::nanosecond +#include "larcorealg/CoreUtils/enumerate.h" +#include "larcorealg/CoreUtils/counter.h" #include "lardataobj/RawData/ExternalTrigger.h" #include "lardataobj/RawData/TriggerData.h" // raw::Trigger #include "lardataobj/Simulation/BeamGateInfo.h" @@ -36,12 +38,17 @@ #include "icaruscode/Decode/DecoderTools/Dumpers/FragmentDumper.h" // dumpFragment() #include "icaruscode/Decode/DecoderTools/details/KeyedCSVparser.h" #include "icaruscode/Decode/DecoderTools/details/KeyValuesData.h" +#include "icaruscode/Decode/ChannelMapping/IICARUSChannelMap.h" +#include "icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h" +#include "icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h" +#include "icaruscode/Utilities/CacheCounter.h" // util::CacheGuard #include "icarusalg/Utilities/BinaryDumpUtils.h" // hexdump() DEBUG #include #include #include // std::setw(), std::setfill() #include +#include #include #include @@ -161,7 +168,11 @@ namespace daq * * `LVDSstatus`: information per PMT wall (i.e. TPC; east first, then * west) of the LVDS signals of the discriminated PMT pairs at the * time of the global trigger. All bits are `0` when trigger - * happened elsewhere. Otherwise, the encoding is currently + * happened elsewhere. Otherwise, the encoding implements the + * prescription documented in `sbn::ExtraTriggerInfo` using + * information from the PMT channel mapping service + * (`icarusDB::IICARUSChannelMap`) if it was configured. Otherwise, + * the encoding is not guaranteed to be correct, and it is * implemented in terms of hardware connectors as follows: * * east wall: `0000` * * west wall: `0000` @@ -211,6 +222,11 @@ namespace daq * module) to be used. Specifying its tag is mandatory, but if it is * explicitly specified empty, the decoder will try to work around its * absence. + * * `AllowDefaultLVDSmap` (flag, default: `false`): if set, when PMT channel + * mapping service is not available a legacy encoding pattern will be used + * for the `LVDSstatus` bits, and a warning will be printed. If unset and + * the PMT channel mapping database is not available, an exception will be + * thrown. * * `DiagnosticOutput` (flag, default: `false`): prints on console trigger * data diagnostics (including a full dump of the parsed content). * * `Debug` (flag, default: `false`): prints on console decoding debug @@ -245,6 +261,7 @@ namespace daq ExtraInfoPtr fTriggerExtra; BeamGateInfoPtr fBeamGateInfo; art::InputTag fTriggerConfigTag; ///< Data product with hardware trigger configuration. + bool fAllowDefaultLVDSmap; ///< Allow LVDS mapping without mapping database. bool fDiagnosticOutput; ///< Produces large number of diagnostic messages, use with caution! bool fDebug; ///< Use this for debugging this tool @@ -252,9 +269,17 @@ namespace daq detinfo::DetectorTimings const fDetTimings; ///< Detector clocks and timings. + /// PMT channel mapping service provider. + icarusDB::IICARUSChannelMapProvider const* fChannelMap = nullptr; + + ///< Tracks the cache of `IICARUSChannelMapProvider`. + util::CacheGuard fChannelMapCacheGuard; + /// Cached pointer to the trigger configuration of the current run, if any. icarus::TriggerConfiguration const* fTriggerConfiguration = nullptr; + /// Map of LVDS bits. + std::optional fPMTpairMap; /// Creates a `ICARUSTriggerInfo` from a generic fragment. icarus::ICARUSTriggerV3Fragment makeTriggerFragment @@ -295,14 +320,20 @@ namespace daq { return static_cast(a) - static_cast(b); } /// Fills one `cryostat` information of `sbn::ExtraTriggerInfo`. - static sbn::ExtraTriggerInfo::CryostatInfo unpackPrimitiveBits( + sbn::ExtraTriggerInfo::CryostatInfo unpackPrimitiveBits( std::size_t cryostat, bool firstEvent, unsigned long int counts, std::uint64_t connectors01, std::uint64_t connectors23 - ); + ) const; + /// Encodes all the `connectorWord` LVDS bits for the specified `cryostat`. + std::array encodeLVDSbits( + short int cryostat, + std::uint64_t connector01word, std::uint64_t connector23word + ) const; + /// Encodes the `connectorWord` LVDS bits from the specified `cryostat` /// and `connector` into the format required by `sbn::ExtraTriggerInfo`. - static std::uint64_t encodeLVDSbits + static std::uint64_t encodeLVDSbitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord); static std::uint16_t encodeSectorBits @@ -328,8 +359,24 @@ namespace daq TriggerDecoderV3::TriggerDecoderV3(fhicl::ParameterSet const &pset) : fDetTimings { art::ServiceHandle()->DataForJob() } + , fChannelMapCacheGuard{ "PMT" } // track the PMT cache only { this->configure(pset); + try { + fChannelMap + = art::ServiceHandle()->provider(); + } + catch (art::Exception const& e) { + if ((e.categoryCode() != art::errors::ServiceNotFound) + || !fAllowDefaultLVDSmap + ) { + throw; + } + mf::LogWarning{ "TriggerDecoderV3" } + << "PMT channel mapping service not available: the correct order" + " of bits in sbn::ExtraTriggerInfo::cryostats is not guaranteed."; + } + if (fChannelMap) fChannelMapCacheGuard.setCache(*fChannelMap); } @@ -351,6 +398,7 @@ namespace daq void TriggerDecoderV3::configure(fhicl::ParameterSet const &pset) { fTriggerConfigTag = pset.get("TrigConfigLabel"); + fAllowDefaultLVDSmap = pset.get("AllowDefaultLVDSmap", false); fDiagnosticOutput = pset.get("DiagnosticOutput", false); fDebug = pset.get("Debug", false); if (pset.has_key("TimeOffset")) { @@ -453,6 +501,29 @@ namespace daq : &(run.getProduct(fTriggerConfigTag)) ; + // refresh the LVDS bit map + if (fChannelMap && fChannelMapCacheGuard.update()) { + // old versions of the database do not have all needed information; + // for the time being, this is tolerated. Once all databases are updated, + // this code should be simplified so that missing information is + // a fatal error. + try { + fPMTpairMap.emplace(*fChannelMap); + } + catch (icarus::trigger::PMTpairBitMap::Exception const& e) { + if ( + e.categoryCode() + != icarus::trigger::PMTpairBitMap::Errors::NoPairBitInformation + ) { + throw; + } + mf::LogWarning("TriggerDecoder") + << "Channel mapping database does not have PMT pair information." + << "\nLegacy, hard-coded mapping will be used."; + fPMTpairMap.reset(); // remove the previous map, if any + } // try ... catch + } + } // TriggerDecoderV3::setupRun() @@ -712,8 +783,9 @@ namespace daq // // fill sbn::ExtraTriggerInfo::cryostats // - auto setCryoInfo = [&extra=*fTriggerExtra,isFirstEvent=(triggerID <= 1)] - (std::size_t cryo, icarus::KeyValuesData const& data) + auto setCryoInfo + = [this,&extra=*fTriggerExtra,isFirstEvent=(triggerID <= 1)] + (std::size_t cryo, icarus::KeyValuesData const& data) { std::string const SIDE = (cryo == sbn::ExtraTriggerInfo::EastCryostat) ? "Cryo1 EAST": "Cryo2 WEST"; @@ -813,17 +885,14 @@ namespace daq sbn::ExtraTriggerInfo::CryostatInfo TriggerDecoderV3::unpackPrimitiveBits( std::size_t cryostat, bool firstEvent, unsigned long int counts, std::uint64_t connectors01, std::uint64_t connectors23 - ) { + ) const { sbn::ExtraTriggerInfo::CryostatInfo cryoInfo; // there is (or was?) a bug on the first event in the run, // which would make this triggerCount wrong cryoInfo.triggerCount = firstEvent? 0UL: counts, - cryoInfo.LVDSstatus = { - encodeLVDSbits(cryostat, 2 /* any of the connectors */, connectors23), - encodeLVDSbits(cryostat, 0 /* any of the connectors */, connectors01) - }; + cryoInfo.LVDSstatus = encodeLVDSbits(cryostat, connectors01, connectors23); cryoInfo.sectorStatus = { encodeSectorBits(cryostat, 2 /* any of the connectors */, connectors23), @@ -834,7 +903,7 @@ namespace daq } // TriggerDecoderV3::unpackPrimitiveBits() - std::uint64_t TriggerDecoderV3::encodeLVDSbits + std::uint64_t TriggerDecoderV3::encodeLVDSbitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord) { /* @@ -854,6 +923,92 @@ namespace daq assert((connectorWord & 0x00FF'FFFF'00FF'FFFF) == ((msw << 32ULL) | lsw)); std::swap(lsw, msw); return (msw << 32ULL) | lsw; + } // TriggerDecoderV3::encodeLVDSbitsLegacy() + + + std::array + TriggerDecoderV3::encodeLVDSbits( + short int cryostat, + std::uint64_t connector01word, std::uint64_t connector23word + ) const { + + if (!fPMTpairMap) { // use legacy approach + return { + encodeLVDSbitsLegacy + (cryostat, sbn::ExtraTriggerInfo::EastPMTwall, connector01word), + encodeLVDSbitsLegacy + (cryostat, sbn::ExtraTriggerInfo::WestPMTwall, connector23word) + }; + } // if no channel mapping + + std::array outputWords; + outputWords.fill(0); + + /* + * The (first) two `LVDSstatus` 64-bit words are initialized according to + * the prescription in the class documentation: LVDS bits from PMT with + * lower channel ID end in lower (least significant) bits in `LVDSstatus`. + * + * The algorithm will fill the bits of the output words in sequence, + * each time picking the value from the appropriate bits in the input + * connector words. The correct bit is determined by a precooked map. + */ + + // connector words have the first connector in the most significant 32 bits: + std::array const connectorBits { + static_cast(connector01word >> 32ULL & 0x00FF'FFFFULL), + static_cast(connector01word & 0x00FF'FFFFULL), + static_cast(connector23word >> 32ULL & 0x00FF'FFFFULL), + static_cast(connector23word & 0x00FF'FFFFULL) + }; + + // --- BEGIN DEBUG ----- + mf::LogTrace debugLog{ "TriggerDecoderV3" }; + debugLog << "Cryostat " << cryostat; + for (auto const [ iConn, word ]: util::enumerate(connectorBits)) { + debugLog + << "\n connector[" << iConn << "]=" << std::hex << word << std::dec; + } + // --- END DEBUG ------- + + auto const mask + = [](auto bitNo){ return 1ULL << static_cast(bitNo); }; + + using icarus::trigger::LVDSbitID; + for (std::size_t const PMTwall: + { sbn::ExtraTriggerInfo::EastPMTwall, sbn::ExtraTriggerInfo::WestPMTwall } + ) { + + for (auto const bit: util::counter(64)) { + + LVDSbitID const bitID{ (unsigned) cryostat, PMTwall, bit }; + + icarus::trigger::LVDSHWbitID const source + = fPMTpairMap->bitSource(bitID).source; + + if (!source) continue; // bit not mapped to anything + + assert(source.cryostat == cryostat); + bool const bitValue + = connectorBits.at(source.connector) & mask(source.bit); + + if (bitValue) { + outputWords[PMTwall] |= mask(bit); + debugLog << "\nLogic LVDS bit " << bitID << " from HW bit " << source; + } + + } // bit + } // PMT wall + + // --- BEGIN DEBUG ----- + for (auto const [ iWord, word ]: util::enumerate(outputWords)) { + debugLog<< "\nStatus[" << cryostat << "][" << iWord << "]=" + << icarus::ns::util::bin(word) + << " (q0x" << std::hex << word << std::dec << ")"; + } + // --- END DEBUG ------- + + return outputWords; } // TriggerDecoderV3::encodeLVDSbits() diff --git a/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx new file mode 100644 index 000000000..cf99c18c2 --- /dev/null +++ b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx @@ -0,0 +1,268 @@ +/** + * @file icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx + * @brief Utilities for mapping CAEN V1730B LVDS channels to PMT channels. + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date March 10, 2024 + * @see icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h + * + */ + +// library header +#include "icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h" + +// ICARUS libraries +#include "icaruscode/Decode/ChannelMapping/IICARUSChannelMapProvider.h" +#include "icaruscode/Decode/ChannelMapping/ICARUSChannelMapDataTypes.h" + +// framework libraries +#include "larcorealg/CoreUtils/enumerate.h" +#include "larcorealg/CoreUtils/counter.h" +#include "messagefacility/MessageLogger/MessageLogger.h" +#include "cetlib_except/exception.h" + +// C/C++ standard libraries +#include +#include // std::sort() +#include // std::dec, std::hex +#include // std::div() +#include + + +// ----------------------------------------------------------------------------- +// --- icarus::trigger::LVDSbitID implementation +// ----------------------------------------------------------------------------- +std::ostream& icarus::trigger::operator<< + (std::ostream& out, LVDSbitID const& bit) +{ + static constexpr char Side[] = "EW"; + auto const fillch = out.fill(); // width is reset to 0, fill character is not + out << (bit.hasCryostat()? Side[bit.cryostat]: '?') + << '/' << (bit.hasPMTwall()? Side[bit.PMTwall]: '?') + << '/' << std::setw(2) << std::setfill('0') << bit.statusBit + << std::setfill(fillch); + return out; +} + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitID::fromIndex(Index_t index) -> LVDSbitID +{ + auto const [ cryostat, wb ] + = std::div(index, NPMTwallsPerCryostat * NStatusWordBits); + auto const [ wall, bit ] = std::div(wb, NStatusWordBits); + return { (std::size_t) cryostat, (std::size_t) wall, (StatusBit_t) bit }; +} // icarus::trigger::LVDSbitID::fromIndex() + + +// ----------------------------------------------------------------------------- +// --- icarus::trigger::LVDSHWbitID implementation +// ----------------------------------------------------------------------------- +std::ostream& icarus::trigger::operator<< + (std::ostream& out, LVDSHWbitID const& bit) +{ + if (!bit) return out << ""; + auto const fillch = out.fill(); // width is reset to 0, fill character is not + out << bit.cryostat << '/' << bit.connector << '/' + << std::setw(2) << std::setfill('0') << bit.bit << std::setfill(fillch); + return out; +} + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSHWbitID::fromIndex(Index_t index) -> LVDSHWbitID { + auto const [ cryostat, cb ] = std::div(index, NConnectorsPerCryostat * 32); + auto const [ connector, bit ] = std::div(cb, 32); + return { + (unsigned short int) cryostat, + (Connector_t) connector, + (ConnectorBit_t) bit + }; +} // icarus::trigger::LVDSHWbitID::fromIndex() + + +// ----------------------------------------------------------------------------- +// --- icarus::trigger::PMTpairBitMap implementation +// ----------------------------------------------------------------------------- +auto icarus::trigger::PMTpairBitMap::bitSource(LVDSbitID bit) const + -> LVDSinfo_t +{ + return fLVDSinfoMap.at(bit.index()); +} + + +// ----------------------------------------------------------------------------- +std::vector> +icarus::trigger::PMTpairBitMap::channelPairs() const { + + std::vector> pairs; + for (LVDSinfo_t const& info: fLVDSinfoMap) { + if (!info.channels.empty()) pairs.push_back(info.channels); + } + + return pairs; +} + +// ----------------------------------------------------------------------------- +auto icarus::trigger::PMTpairBitMap::buildLVDSsourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const + -> std::vector +{ + // + // get the LVDS connector/bit information channel by channel + // + std::vector PMTchannelDB; + + mf::LogTrace debugLog{ "PMTpairBitMap" }; + debugLog << "PMTpairBitMap::buildLVDSsourceMap() collecting channel info:"; + + for (unsigned int const fragmentID: util::counter(0x2000, 0x2018)) { + if (!channelMap.hasPMTDigitizerID(fragmentID)) { + throw Exception{ Errors::NoPMTfragment } + << "PMT fragment ID 0x" << std::hex << fragmentID << std::dec + << " not available in the channel mapping database.\n"; + } + + for (icarusDB::PMTChannelInfo_t const& info + : channelMap.getPMTchannelInfo(fragmentID) + ) { + unsigned short int const cryostat = info.channelID / NChannelsPerCryostat; + + if (!info.hasLVDSinfo()) { + throw Exception{ Errors::NoPairBitInformation } + << "PMT fragment ID 0x" << std::hex << fragmentID << std::dec + << " has no LVDS information in the channel mapping database.\n"; + } + + debugLog << "\n[#" << PMTchannelDB.size() << "] CH=" << info.channelID + << " " << cryostat << "/" << info.LVDSconnector << "/" << info.LVDSbit; + + PMTchannelDB.push_back(LVDSchannel_t{ + info.channelID // .channel + , LVDSHWbitID{ // .LVDSsource + cryostat // .cryostat + , info.LVDSconnector // .connector + , info.LVDSbit // .bit + } + }); + } // for channel in fragment + } // for fragment ID + + assert(PMTchannelDB.size() == NChannels); // insider knowledge... + + std::sort(PMTchannelDB.begin(), PMTchannelDB.end()); + + // + // fill the logic LVDS bit positions with their source information + // + + /* + * The logic of the filling is a bit complicated. Class documentation explains + * it and shows it to be: + * + * east cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `0` the most + * significant bit in `a` (bit #55) and channel `45` the one in `d` (bit 23); + * east cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `90` the most + * significant bit in `a` (bit #55) and channel `135` the one in `d` (bit 23); + * west cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `180` the most + * significant bit in `a` (bit #55) and channel `225` the one in `d` (bit 23); + * west cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `270` the most + * significant bit in `a` (bit #55) and channel `315` the one in `d` (bit 23). + * + * How we get there: + * * we sort the filling with an ordered progression of channels, lowest + * first, and because most LVDS bit are associated to two channels, we + * track the channels we already covered when processing the other PMT + * channel of the pair, and skip them when met those LVDS bit again + * * the destination bit is also an ordered progression within the word, + * but inverted: note in fact that if we inverted the position of the bits + * in each 32-bit word + */ + std::vector sourceMap{ LVDSbitID::NIndices() }; + std::vector HWtoLogicID{ LVDSHWbitID::NIndices() }; + + unsigned int nBits = 0; + for (LVDSchannel_t const& info: PMTchannelDB) { // in PMT channel order + + LVDSHWbitID::Index_t const sourceIndex = info.LVDSsource.index(); + + LVDSbitID logicBit = HWtoLogicID[sourceIndex]; + + LVDSinfo_t* mapInfo = logicBit? &(sourceMap[logicBit.index()]): nullptr; + if (!mapInfo) { // new bit: set the source + // which logic bit to set: + logicBit.cryostat = info.LVDSsource.cryostat; + assert(logicBit.cryostat == info.channel / NChannelsPerCryostat); + logicBit.PMTwall + = info.channel / (NChannelsPerCryostat/LVDSbitID::NPMTwallsPerCryostat) + % LVDSbitID::NCryostats; + logicBit.statusBit = 47 - (nBits++ % 48); // 0 => 47, 1 => 46,..., 47 => 0 + if (logicBit.statusBit >= 24) + logicBit.statusBit += 8; // 0 => 0,..., 23 => 23, 24 => 32, 25 => 33,... + + assert(logicBit); + HWtoLogicID[sourceIndex] = logicBit; + mapInfo = &(sourceMap[logicBit.index()]); + mapInfo->source = info.LVDSsource; + } + assert(mapInfo); + assert(mapInfo->source == info.LVDSsource); + + mapInfo->channels.push_back(info.channel); + // check that they are sorted (the input list should be sorted already..._ + assert((mapInfo->channels.size() == 1) + || ( + mapInfo->channels[mapInfo->channels.size()-2] + < mapInfo->channels[mapInfo->channels.size()-1] + )); + + } // for PMTchannelDB + + + // --- BEGIN DEBUG ----- + debugLog << "\nFinal map:"; + for (auto const& [ bit, info ]: util::enumerate(sourceMap)) { + debugLog << "\n[ " << std::setw(3) << bit << "] logic=" + << LVDSbitID::fromIndex(bit) << " => HW="; + if (info.source) { + debugLog << info.source << " (ix=" + << std::setw(3) << info.source.index() << ") " + << info.channels.size() << " channels {"; + for (raw::Channel_t const channel: info.channels) + debugLog << " " << std::setw(3) << channel; + debugLog << " }"; + } + else { + debugLog << "(invalid)"; + } + } + // --- END DEBUG ------- + + assert(nBits == 192); + return sourceMap; + +} // icarus::trigger::PMTpairBitMap::buildLVDSsourceMap() + + +// ----------------------------------------------------------------------------- +std::string icarus::trigger::PMTpairBitMap::errorMessage(Errors code) { + using namespace std::string_literals; + switch (code) { + case Errors::NoError: + return "no error"s; + case Errors::Error: + return "(unspecified) error"s; + case Errors::NoDatabase: + return "channel mapping database not configured"s; + case Errors::NoPMTfragment: + return "channel mapping database does not have PMT fragment information"s; + case Errors::NoPairBitInformation: + return "channel mapping database does not have PMT pair bit information"s; + case Errors::Unknown: + return "unknown error"s; + default: + return "unexpected error code "s + std::to_string(static_cast(code)); + } // switch +} // icarus::trigger::PMTpairBitMap::errorMessage() + + +// ----------------------------------------------------------------------------- diff --git a/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h new file mode 100644 index 000000000..3b6dae93f --- /dev/null +++ b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h @@ -0,0 +1,450 @@ +/** + * @file icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h + * @brief Utilities for mapping CAEN V1730B LVDS channels to PMT channels. + * @author Gianluca Petrillo (petrillo@slac.stanford.edu) + * @date March 10, 2024 + * @see icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx + * + */ + +#ifndef ICARUSCODE_PMT_TRIGGER_ALGORITHMS_LVDSBITMAPS_H +#define ICARUSCODE_PMT_TRIGGER_ALGORITHMS_LVDSBITMAPS_H + +// LArSoft and framework libraries +#include "lardataobj/RawData/OpDetWaveform.h" // raw::Channel_t +#include "cetlib_except/coded_exception.h" + +// C/C++ standard libraries +#include // std::ostream +#include +#include +#include +#include + +// ----------------------------------------------------------------------------- +// forward declarations +namespace icarus::trigger { + + struct LVDSbitID; + struct LVDSHWbitID; + struct LVDSinfo_t; + + class PMTpairBitMap; + + std::ostream& operator<< (std::ostream& out, LVDSbitID const& bit); + std::ostream& operator<< (std::ostream& out, LVDSHWbitID const& bit); + +} // namespace icarus::trigger + +namespace icarusDB { class IICARUSChannelMapProvider; } + + +// ----------------------------------------------------------------------------- +/// Identifier of a "logical" LVDS bit +/// following `sbn::ExtraTriggerInfo` convention. +struct icarus::trigger::LVDSbitID { + + using StatusBit_t = unsigned short int; ///< Type for bit in the status word. + using Index_t = unsigned short int; ///< Type for LVDS bit ID index. + + /// How many cryostats in the detector. + static constexpr std::size_t NCryostats = 2; + + /// How many PMT walls per cryostat. Each has one status word. + static constexpr std::size_t NPMTwallsPerCryostat = 2; + + /// How many bits in a status word. + static constexpr unsigned short int NStatusWordBits = 64; + + + /// Special value to identify the absence of cryostat information. + static constexpr std::size_t NoCryostat + = std::numeric_limits::max(); + + /// Special value to identify the absence of PMT wall information. + static constexpr std::size_t NoPMTwall + = std::numeric_limits::max(); + + /// Special value to identify the absence of a status bit. + static constexpr StatusBit_t NoStatusBit + = std::numeric_limits::max(); + + /// Special value for an invalid bit index. + static constexpr Index_t InvalidIndex = std::numeric_limits::max(); + + + // --- BEGIN --- Data members -------------------------------------------- + + /// The cryostat the bit comes from (`sbn::ExtraTriggerInfo` convention). + std::size_t cryostat = NoCryostat; + + /// The PMT wall the bit comes from (`sbn::ExtraTriggerInfo` convention). + std::size_t PMTwall = NoPMTwall; + + /// The number of bit in the status word (`0` is the least significant one). + StatusBit_t statusBit = NoStatusBit; + + // --- END ----- Data members -------------------------------------------- + + /// Returns if the cryostat is set; ID is valid even if this is `false`. + constexpr bool hasCryostat() const { return cryostat != NoCryostat; } + + /// Returns if the PMT wall is set; ID is valid even if this is `false`. + constexpr bool hasPMTwall() const { return PMTwall != NoPMTwall; } + + /// Returns an "unique" index for this information. + /// Supports the absence of PMT wall or cryostat (not of bit). + constexpr Index_t index() const + { + if (!isValid()) return InvalidIndex; + std::size_t const nWalls = hasPMTwall() + ? (PMTwall + (hasCryostat()? cryostat: 0) * NPMTwallsPerCryostat): 0; + return statusBit + nWalls * NStatusWordBits; + } + + + /// Returns whether this ID is valid + /// (does not require `hasCryostat()` nor `hasPMTwall()`). + constexpr bool isValid() const { return statusBit != NoStatusBit; } + + /// Returns whether this ID is valid + /// (does not require `hasCryostat()` nor `hasPMTwall()`). + constexpr operator bool() const { return isValid(); } + + + /// Rebuilds an ID object from its index. + static LVDSbitID fromIndex(Index_t index); + + /// Returns the number of possible indices (`0` to `NIndices() - 1`). + static constexpr Index_t NIndices() + { return NCryostats * NPMTwallsPerCryostat * NStatusWordBits; } + +}; // icarus::trigger::LVDSbitID + + +// ----------------------------------------------------------------------------- +/// Identifier of a "hardware" LVDS bit featured in the PMT channel mapping +/// databases. +struct icarus::trigger::LVDSHWbitID { + + using Connector_t = unsigned short int; ///< Type for connector number. + using ConnectorBit_t = unsigned short int; ///< Type for connector bit. + using Index_t = unsigned short int; ///< Type for LVDS bit ID index. + + /// How many cryostats in the detector. + static constexpr unsigned short int NCryostats = LVDSbitID::NCryostats; + + /// How many 32-bit connectors come out of each cryostat. + static constexpr unsigned short int NConnectorsPerCryostat = 4; + + /// Special value to identify the absence of cryostat information. + static constexpr unsigned short int NoCryostat + = std::numeric_limits::max(); + + /// Special value to identify the absence of a connector. + static constexpr Connector_t NoConnector + = std::numeric_limits::max(); + + /// Special value to identify the absence of a connector bit. + static constexpr ConnectorBit_t NoConnectorBit + = std::numeric_limits::max(); + + /// Special value for an invalid bit index. + static constexpr Index_t InvalidIndex = std::numeric_limits::max(); + + + // --- BEGIN --- Data members -------------------------------------------- + + /// The cryostat the bit comes from (`sbn::ExtraTriggerInfo` convention). + unsigned short int cryostat = NoCryostat; + + /// The number of connector within the appropriate wall. + Connector_t connector = NoConnector; + + /// The bit index within the 32-bit connector word (0 = least significant). + ConnectorBit_t bit = NoConnectorBit; + + // --- END ----- Data members -------------------------------------------- + + /// Returns if the cryostat is set; ID is valid even if this is `false`. + constexpr bool hasCryostat() const { return cryostat != NoCryostat; } + + /// Returns an "unique" index for this information. + constexpr Index_t index() const + { + return isValid() + ? ((hasCryostat()? (cryostat * NConnectorsPerCryostat * 32): 0) + + connector * 32 + bit) + : InvalidIndex; + } + + + /// Returns whether this ID is valid (does not require `hasCryostat()`). + constexpr bool isValid() const + { return (connector != NoConnector) && (bit != NoConnectorBit); } + + /// Returns whether this ID is valid (does not require `hasCryostat()`). + constexpr operator bool() const { return isValid(); } + + + /// Rebuilds an ID object from its index. + static LVDSHWbitID fromIndex(Index_t index); + + /// Returns the number of possible indices (`0` to `NIndices() - 1`). + static constexpr Index_t NIndices() + { return NCryostats * NConnectorsPerCryostat * 32; } + +}; // icarus::trigger::LVDSHWbitID + + +// ----------------------------------------------------------------------------- +/// Description of a (logical) LVDS bit and additional information +/// (none so far). +struct icarus::trigger::LVDSinfo_t { + + LVDSHWbitID source; ///< Identifier of the hardware LVDS bit feeding this one. + + std::vector channels; ///< PMT channels covered by this bit. + + /// Returns whether this information is fully valid (both channel and ID). + operator bool() const { return source; } + + /// Order operator: sorted after the LVDS index. + bool operator< (LVDSinfo_t const& other) const + { return source.index() < other.source.index(); } + +}; // icarus::trigger::LVDSinfo_t + + +// ----------------------------------------------------------------------------- +/** + * @brief Utility to build and maintain ICARUS trigger LVDS bit maps. + * + * This object caches the relations between the LVDS bits. The maps include: + * * from logic LVDS bit ID to hardware LVDS bit ID (see below). + * + * Upon request, the following mappings may be added: + * * from PMT channel to LVDS bit ID (logic), and vice versa. + * + * + * Maps are build based on the information from the PMT hardware channel + * mapping. + * + * + * + * Mapping conventions + * -------------------- + * + * The logic mapping is described in `sbn::ExtraTriggerInfo::LVDSstatus`. + * Compared with that mapping, the LVDS bit ID are assigned so that + * * the index of the ID, modulo 64 bits, matches the `LVDSstatus` words; + * * cryostats and PMT walls of the IDs are honoured, so that they can be used + * to directly adderss `LVDSstatus`. + * + * This results in the following mapping, "first bit" meaning the most significant: + * + * * east cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `0` the most + * significant bit in `a` (bit #55) and channel `45` the one in `d` (bit 23); + * * east cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `90` the most + * significant bit in `a` (bit #55) and channel `135` the one in `d` (bit 23); + * * west cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `180` the most + * significant bit in `a` (bit #55) and channel `225` the one in `d` (bit 23); + * * west cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `270` the most + * significant bit in `a` (bit #55) and channel `315` the one in `d` (bit 23). + * + * + * Naming conventions + * ------------------- + * + * Each PMT has a channel ID associated with a location; the relation between + * the channel number value and the position of the PMT is fixed by a + * convention (from LArSoft). This is a "logical" channel number. + * On the other side, each PMT has a unique identifier that was assigned by + * the ICARUS PMT Working Group and is used as key in an hardware database. + * This is the PMT hardware channel number. These two channel numbers may be + * (and in fact, are) different. + * A similar distinction is made of the LVDS signals. + * + * * Eight LVDS signals with discriminated PMT pair information are emitted by + * each CAEN V1730B PMT readout board. These bits are collected into four + * 64-bit words, one for each of the four PMT walls. The word values are + * stored in `sbn::ExtraTriggerInfo`, which also defines the convention + * relating each of the bits in the 64-bit words with the physical location of + * the PMT feeding the bit. This is equivalent to the logical PMT channel + * number described above, and here we call it a "logical LVDS bit ID". + * Analyzers will typically deal with this logical ID only. It is described by + * `icarus::trigger::LVDSbitID` data type. + * * The assembly of the bits in hardware includes the combination of 8-bit + * information from groups of three PMT readout boards, physically using LVDS + * format and for that often just dubbed "the LVDS signals", via custom LVDS + * boxes into 32-bit cables and connectors, with the excess 8 bits in each of + * them partly used for other information. + * Like in the case of single PMT, we define a "hardware LVDS bit ID" + * representing the position of a bit in the 32-bit word in the connector, + * and identifying the connector. The relation of these bits with the actual + * PMT channels is encoded in the hardware database; analyzers typically don't + * have to deal with them, because the trigger decoding software will + * translate them into the `sbn::ExtraTriggerInfo` convention (using, in fact, + * this `PMTpairBitMap` object for the mapping). The data type devoted to this + * concept is `LVDSHWbitID`. Note that the NIM FPGA boards combine two 32-bit + * connector words into 64-bit words for transportation. These 64-bit words + * are stored in the trigger DAQ fragment. At first look, these 64-bit words + * are similar to the ones described in the previous bullet, but their bits + * are in a different relation with the actual readout boards and PMT. + * Also note that the decoding of these 64-bit words from the FPGA require, + * on top oft he information from this mapping, the information of where each + * connector is located within the word (hint: connectors 0 and 2 are stored + * as the most significant 32-bit word, 1 and 3 as the least significant one). + * + */ +class icarus::trigger::PMTpairBitMap { + + public: + + // --- BEGIN --- Hard-coded geometry ----------------------------------------- + /// @name Good old hard-coded ICARUS geometry. + /// @{ + + /// Total number of PMT channels in the detector. + static constexpr unsigned int NChannels = 360; + + /// Number of PMT channels in each cryostat. + static constexpr unsigned int NChannelsPerCryostat = 180; + + /// Number of cryostats in the detector. + static constexpr std::size_t NCryostats = NChannels / NChannelsPerCryostat; + + ///@} + // --- END ----- Hard-coded geometry ----------------------------------------- + + /// Error codes from this object. See `errorMessage()` code for descriptions. + enum class Errors { + NoError, ///< No error condition. + Error, ///< An unspecified error condition. + NoDatabase, ///< No channel mapping database access. + NoPMTfragment, ///< Missing a required PMT fragment information. + NoPairBitInformation, ///< No PMT pair bit information in the database. + Unknown ///< Unknown error. + }; + + /// Returns a predefined message describing the specified error `code`. + static std::string errorMessage(Errors code); + + /// Type of exception thrown by this object. + using Exception = cet::coded_exception; + + /// Default constructor: maps empty until `rebuild()` is called. + PMTpairBitMap() = default; + + /// Constructor: builds all the maps based on the specified PMT channel map. + PMTpairBitMap(icarusDB::IICARUSChannelMapProvider const& channelMap) + { buildAllMaps(channelMap); } + + + // --- BEGIN ----- Query interface ----------------------------------------- + /// @name Query interface. + /// @{ + + /** + * @brief Returns the source bit for the specified encoded bit. + * @param bit ID of the logical LVDS bit to get the source information of + * @return the number of hardware connector and bit where to find the value + * @throws std::out_of_range if the specified bit is not in the map + * + * The output `connector`/`bit` bits are sorted according to the prescription + * of `sbn::ExtraTriggerInfo`, which requires the least significant bit to + * have the most upstream PMT pair (i.e. the lowest channels). + * + * The returned information includes: + * * the cryostat the bit comes from (the same as the one in `bit`); + * * the hardware connector number the bit comes from; + * * the hardware bit (wire) in the connector which carries the `bit` value. + * + * See `buildLVDSsourceMap()` for details of the mapping definitions. + */ + LVDSinfo_t bitSource(LVDSbitID bit) const; + + + /** + * @brief Returns a list of channels paired in a single LVDS. + * @return a list of vectors of one or two PMT channel ID + * + * This method recreates the pairing of PMT channels into LVDS bits. + * The order of the "pairs" follows the order of the logic bits. + * These "pairs" can actually contain either two or one ID; empty pairs + * (from invalid bits) are not included. + */ + std::vector> channelPairs() const; + + /// @} + // --- END ------- Query interface ----------------------------------------- + + + // --- BEGIN ----- Map updates --------------------------------------------- + + /// Rebuilds all the LVDS maps based on the specified channel map status. + void rebuild(icarusDB::IICARUSChannelMapProvider const& channelMap) + { buildAllMaps(channelMap); } + + // --- END ------- Map updates --------------------------------------------- + + + private: + + /// Description of a LVDS bit and association to a PMT channel. + /// @note This record does not include the logical LVDS bit ID it provides + /// information of. + struct LVDSchannel_t { + + /// Special value identifying no channel set. + static constexpr raw::Channel_t NoChannel + = std::numeric_limits::max(); + + raw::Channel_t channel = NoChannel; ///< ID of an associated PMT channel. + + LVDSHWbitID LVDSsource; ///< Identifier of the LVDS bit source. + + /// Returns an "unique" index for the LVDS source. + constexpr unsigned short int index() const { return LVDSsource.index(); } + + /// Returns whether this information is fully valid (both channel and ID). + operator bool() const { return (channel != NoChannel) && LVDSsource; } + + /// Order operator: sorted after the channel ID only. + bool operator< (LVDSchannel_t const& other) const + { return channel < other.channel; } + + }; // LVDSchannel_t + + + /// For each logic LVDS bit index, the HW LVDS ID and information are stored. + std::vector fLVDSinfoMap; + + + /// Builds all the maps + void buildAllMaps(icarusDB::IICARUSChannelMapProvider const& channelMap) + { + fLVDSinfoMap = buildLVDSsourceMap(channelMap); + } + + /** + * @brief Builds the LVDS map connecting each bit to its hardware source. + * @param channelMap service provider to associate each PMT channel to LVDS + * @return a map + * + * The map is multi-level. The first level is the cryostat index (up to + * `NCryostats - 1`), then for each cryostat a vector hosts in each index + * the information of the LVDS ID matching that index. + * The information includes the source in the hardware connector of the bit. + * + */ + std::vector buildLVDSsourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const; + + +}; // icarus::trigger::PMTpairBitMap + + +// ----------------------------------------------------------------------------- + +#endif // ICARUSCODE_PMT_TRIGGER_ALGORITHMS_LVDSBITMAPS_H From 00cdbf9267655140ffd4ac807d4fc5bd432f9b7d Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Fri, 15 Mar 2024 20:16:24 -0500 Subject: [PATCH 08/12] Channel mapping: extended support for missing connector information --- .../ChannelMapping/ChannelMapPostGres.cxx | 20 +++++++++++-------- .../ChannelMapping/ChannelMapSQLite.cxx | 17 +++++++++++++--- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx index 80dbb47bc..3c8a20aeb 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapPostGres.cxx @@ -490,10 +490,12 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap << " on row " << row << ") retrieving LVDS connector from channel mapping database\n"; } - auto const [ LVDSconnector, LVDSbit ] - = splitIntegers<2, unsigned short int>(*LVDSconnectorLabel, "-"); - chInfo.LVDSconnector = LVDSconnector; - chInfo.LVDSbit = LVDSbit; + if (!LVDSconnectorLabel->empty() && (*LVDSconnectorLabel != "-")) { + auto const [ LVDSconnector, LVDSbit ] + = splitIntegers<2, unsigned short int>(*LVDSconnectorLabel, "-"); + chInfo.LVDSconnector = LVDSconnector; + chInfo.LVDSbit = LVDSbit; + } } // adder connector and bit @@ -505,10 +507,12 @@ int icarusDB::ChannelMapPostGres::BuildPMTFragmentToDigitizerChannelMap << " on row " << row << ") retrieving adder connector from channel mapping database\n"; } - auto const [ adderConnector, adderBit ] - = splitIntegers<2, unsigned short int>(*adderConnectorLabel, "-"); - chInfo.adderConnector = adderConnector; - chInfo.adderBit = adderBit; + if (!adderConnectorLabel->empty() && (*adderConnectorLabel != "-")) { + auto const [ adderConnector, adderBit ] + = splitIntegers<2, unsigned short int>(*adderConnectorLabel, "-"); + chInfo.adderConnector = adderConnector; + chInfo.adderBit = adderBit; + } } // fill the map diff --git a/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx b/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx index 4ccad3a3a..cf2454e25 100644 --- a/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx +++ b/icaruscode/Decode/ChannelMapping/ChannelMapSQLite.cxx @@ -27,6 +27,7 @@ // C++ standard libraries #include // std::transform() #include +#include #include #include // std::tolower() @@ -481,17 +482,27 @@ int icarusDB::ChannelMapSQLite::buildPMTFragmentToDigitizerChannelMap_callback << "' into a channel number for channel " << channelID << "!\n"; } - // PMT discriminated signal connector and bit + // connector bits; if the column is not present or if the value is empty or + // just "-", fall back to invalid values + auto const isValidConnectorBit = [argv,argc](std::size_t column) + { + if (column >= (std::size_t) argc) return false; + std::string_view const value = argv[column]; + return !value.empty() && (value != "-"); + }; + + // PMT discriminated signal connector and bit; auto const [ LVDSconnector, LVDSbit ] - = (LVDSconnectorColumn < (std::size_t)argc) + = isValidConnectorBit(LVDSconnectorColumn) ? splitIntegers<2, unsigned short int>(argv[LVDSconnectorColumn], "-") : std::array { PMTChannelInfo_t::NoConnector, PMTChannelInfo_t::NoConnectorBit } ; // adder discriminated signal connector and bit + // if the column is not present or if the value is empty or "-", fall back auto const [ adderConnector, adderBit ] - = (AdderConnectorColumn < (std::size_t)argc) + = isValidConnectorBit(AdderConnectorColumn) ? splitIntegers<2, unsigned short int>(argv[AdderConnectorColumn], "-") : std::array { PMTChannelInfo_t::NoConnector, PMTChannelInfo_t::NoConnectorBit } From 92d22c4ad516af14c2953f93791f5fd6de3c26bf Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Tue, 19 Mar 2024 18:49:37 -0500 Subject: [PATCH 09/12] Trigger decoder uses channel mapping for adder bit decoding An updated database is necessary. If an older one is used, a legacy map will be used (which is known not to be completely correct). --- .../DecoderTools/TriggerDecoderV3_tool.cc | 189 +++++++++--- .../PMT/Trigger/Algorithms/LVDSbitMaps.cxx | 281 +++++++++++++---- .../PMT/Trigger/Algorithms/LVDSbitMaps.h | 285 ++++++++++++++---- 3 files changed, 589 insertions(+), 166 deletions(-) diff --git a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc index 56b19b495..fc8ea25ea 100644 --- a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc +++ b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc @@ -173,17 +173,25 @@ namespace daq * information from the PMT channel mapping service * (`icarusDB::IICARUSChannelMap`) if it was configured. Otherwise, * the encoding is not guaranteed to be correct, and it is - * implemented in terms of hardware connectors as follows: - * * east wall: `0000` - * * west wall: `0000` + * implemented in terms of hardware connector ports as follows: + * * east wall: `0000` + * * west wall: `0000` * * For the expected matching with PMT, see the documentation of * `sbn::ExtraTriggerInfo::CryostatInfo::LVDSstatus`. * * `sectorStatus`: information for detector sector (one per TPC), * reporting the discrimination status of the adder signals at - * the time of the global trigger: `00000000 00NnnssS`, with `S` - * (least significant bit) the southernmost adder, and then - * northward until the northernmost one, `N`. + * the time of the global trigger. All bits are `0` when trigger + * happened elsewhere. Otherwise, the encoding implements the + * prescription documented in `sbn::ExtraTriggerInfo` using + * information from the PMT channel mapping service + * (`icarusDB::IICARUSChannelMap`) if it was configured. Otherwise, + * the encoding is not guaranteed to be correct, and it is + * implemented extracting bits from port 3 of the connectors: + * * east wall: `0000'0000'00ss'snnn` with = `0000'0sss` + * and = `0000'0nnn` + * * west wall: `0000'0000'00ss'snnn` with = `0000'0sss` + * and = `0000'0nnn` * * Information may be missing. If a count is not available, its value is * set to `0` (which is an invalid value because their valid range starts @@ -279,7 +287,7 @@ namespace daq icarus::TriggerConfiguration const* fTriggerConfiguration = nullptr; /// Map of LVDS bits. - std::optional fPMTpairMap; + std::optional fPMTpairMap; /// Creates a `ICARUSTriggerInfo` from a generic fragment. icarus::ICARUSTriggerV3Fragment makeTriggerFragment @@ -322,21 +330,28 @@ namespace daq /// Fills one `cryostat` information of `sbn::ExtraTriggerInfo`. sbn::ExtraTriggerInfo::CryostatInfo unpackPrimitiveBits( std::size_t cryostat, bool firstEvent, unsigned long int counts, - std::uint64_t connectors01, std::uint64_t connectors23 + std::uint64_t connectors01, std::uint64_t connectors23, + sbn::bits::triggerLogicMask triggerLogic ) const; - /// Encodes all the `connectorWord` LVDS bits for the specified `cryostat`. + /// Encodes all the LVDS bits for the specified `cryostat`. std::array encodeLVDSbits( short int cryostat, std::uint64_t connector01word, std::uint64_t connector23word ) const; + /// Encodes all the adder bits for the specified `cryostat`. + std::array encodeSectorBits( + short int cryostat, + std::uint64_t connector01word, std::uint64_t connector23word + ) const; + /// Encodes the `connectorWord` LVDS bits from the specified `cryostat` /// and `connector` into the format required by `sbn::ExtraTriggerInfo`. static std::uint64_t encodeLVDSbitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord); - static std::uint16_t encodeSectorBits + static std::uint16_t encodeSectorBitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord); /// Returns the `nBits` bits of `value` from `startBit` on. @@ -504,24 +519,19 @@ namespace daq // refresh the LVDS bit map if (fChannelMap && fChannelMapCacheGuard.update()) { // old versions of the database do not have all needed information; - // for the time being, this is tolerated. Once all databases are updated, - // this code should be simplified so that missing information is - // a fatal error. - try { - fPMTpairMap.emplace(*fChannelMap); + // this is going to be supported since the older run periods will never + // have that information. + fPMTpairMap.emplace(*fChannelMap); + if (!fPMTpairMap->hasMap(icarus::trigger::LVDSbitMaps::Map::PMTpairs)) { + mf::LogWarning("TriggerDecoder") + << "PMT pair mapping could not be extracted." + << " A legacy, hard-coded one will be used."; } - catch (icarus::trigger::PMTpairBitMap::Exception const& e) { - if ( - e.categoryCode() - != icarus::trigger::PMTpairBitMap::Errors::NoPairBitInformation - ) { - throw; - } + if (!fPMTpairMap->hasMap(icarus::trigger::LVDSbitMaps::Map::Adders)) { mf::LogWarning("TriggerDecoder") - << "Channel mapping database does not have PMT pair information." - << "\nLegacy, hard-coded mapping will be used."; - fPMTpairMap.reset(); // remove the previous map, if any - } // try ... catch + << "Adder mapping could not be extracted." + << " A legacy, hard-coded one will be used."; + } } } // TriggerDecoderV3::setupRun() @@ -894,10 +904,8 @@ namespace daq cryoInfo.LVDSstatus = encodeLVDSbits(cryostat, connectors01, connectors23); - cryoInfo.sectorStatus = { - encodeSectorBits(cryostat, 2 /* any of the connectors */, connectors23), - encodeSectorBits(cryostat, 0 /* any of the connectors */, connectors01) - }; + cryoInfo.sectorStatus + = encodeSectorBits(cryostat, connectors01, connectors23); return cryoInfo; } // TriggerDecoderV3::unpackPrimitiveBits() @@ -932,7 +940,10 @@ namespace daq std::uint64_t connector01word, std::uint64_t connector23word ) const { - if (!fPMTpairMap) { // use legacy approach + if (!fPMTpairMap + || !fPMTpairMap->hasMap(icarus::trigger::LVDSbitMaps::Map::PMTpairs) + ) { + // use legacy approach return { encodeLVDSbitsLegacy (cryostat, sbn::ExtraTriggerInfo::EastPMTwall, connector01word), @@ -964,24 +975,22 @@ namespace daq // --- BEGIN DEBUG ----- mf::LogTrace debugLog{ "TriggerDecoderV3" }; - debugLog << "Cryostat " << cryostat; - for (auto const [ iConn, word ]: util::enumerate(connectorBits)) { - debugLog - << "\n connector[" << iConn << "]=" << std::hex << word << std::dec; - } + debugLog << "Cryostat " << cryostat << ":"; + for (auto const [ iConn, word ]: util::enumerate(connectorBits)) + debugLog << " [" << iConn << "]=0x" << std::hex << word << std::dec; // --- END DEBUG ------- auto const mask = [](auto bitNo){ return 1ULL << static_cast(bitNo); }; - using icarus::trigger::LVDSbitID; + using icarus::trigger::PMTpairBitID; for (std::size_t const PMTwall: { sbn::ExtraTriggerInfo::EastPMTwall, sbn::ExtraTriggerInfo::WestPMTwall } ) { - for (auto const bit: util::counter(64)) { + for (auto const bit: util::counter(64)) { - LVDSbitID const bitID{ (unsigned) cryostat, PMTwall, bit }; + PMTpairBitID const bitID{ (unsigned) cryostat, PMTwall, bit }; icarus::trigger::LVDSHWbitID const source = fPMTpairMap->bitSource(bitID).source; @@ -1002,9 +1011,9 @@ namespace daq // --- BEGIN DEBUG ----- for (auto const [ iWord, word ]: util::enumerate(outputWords)) { - debugLog<< "\nStatus[" << cryostat << "][" << iWord << "]=" + debugLog<< "\nPMT pair status[" << cryostat << "][" << iWord << "]=" << icarus::ns::util::bin(word) - << " (q0x" << std::hex << word << std::dec << ")"; + << " (0x" << std::hex << word << std::dec << ")"; } // --- END DEBUG ------- @@ -1012,6 +1021,92 @@ namespace daq } // TriggerDecoderV3::encodeLVDSbits() + std::array + TriggerDecoderV3::encodeSectorBits( + short int cryostat, + std::uint64_t connector01word, std::uint64_t connector23word + ) const { + + if (!fPMTpairMap + || !fPMTpairMap->hasMap(icarus::trigger::LVDSbitMaps::Map::Adders) + ) { + // use legacy approach + return { + encodeSectorBitsLegacy(cryostat, 2, connector23word), + encodeSectorBitsLegacy(cryostat, 0, connector01word) + }; + } // if no channel mapping + + std::array outputWords; + outputWords.fill(0); + + /* + * The (first) two `LVDSstatus` 64-bit words are initialized according to + * the prescription in the class documentation: LVDS bits from PMT with + * lower channel ID end in lower (least significant) bits in `LVDSstatus`. + * + * The algorithm will fill the bits of the output words in sequence, + * each time picking the value from the appropriate bits in the input + * connector words. The correct bit is determined by a precooked map. + */ + + // connector words have the first connector in the most significant 32 bits: + std::array const connectorBits { + static_cast(connector01word >> 32ULL & 0xFF00'0000ULL), + static_cast(connector01word & 0xFF00'0000ULL), + static_cast(connector23word >> 32ULL & 0xFF00'0000ULL), + static_cast(connector23word & 0xFF00'0000ULL) + }; + + // --- BEGIN DEBUG ----- + mf::LogTrace debugLog{ "TriggerDecoderV3" }; + debugLog << "Cryostat " << cryostat << ":"; + for (auto const [ iConn, word ]: util::enumerate(connectorBits)) + debugLog << " [" << iConn << "]=0x" << std::hex << word << std::dec; + // --- END DEBUG ------- + + auto const mask + = [](auto bitNo){ return 1ULL << static_cast(bitNo); }; + + using icarus::trigger::AdderBitID; + for (std::size_t const PMTwall: + { sbn::ExtraTriggerInfo::EastPMTwall, sbn::ExtraTriggerInfo::WestPMTwall } + ) { + + for (auto const bit: util::counter(16)) { + + AdderBitID const bitID{ (unsigned) cryostat, PMTwall, bit }; + + icarus::trigger::LVDSHWbitID const source + = fPMTpairMap->bitSource(bitID).source; + + if (!source) continue; // bit not mapped to anything + + assert(source.cryostat == cryostat); + bool const bitValue + = connectorBits.at(source.connector) & mask(source.bit); + + if (bitValue) { + outputWords[PMTwall] |= mask(bit); + debugLog + << "\nLogic adder bit " << bitID << " from HW bit " << source; + } + + } // bit + } // PMT wall + + // --- BEGIN DEBUG ----- + for (auto const [ iWord, word ]: util::enumerate(outputWords)) { + debugLog<< "\nAdder status[" << cryostat << "][" << iWord << "]=" + << icarus::ns::util::bin(word) + << " (0x" << std::hex << word << std::dec << ")"; + } + // --- END DEBUG ------- + + return outputWords; + } // TriggerDecoderV3::encodeSectorBits() + + template constexpr T TriggerDecoderV3::bits(T value) { constexpr T nBitsMask = (nBits == sizeof(T)*8)? ~T{0}: T((1 << nBits) - 1); @@ -1019,7 +1114,7 @@ namespace daq } - std::uint16_t TriggerDecoderV3::encodeSectorBits + std::uint16_t TriggerDecoderV3::encodeSectorBitsLegacy (short int cryostat, short int connector, std::uint64_t connectorWord) { /* @@ -1029,8 +1124,7 @@ namespace daq * * connector 2: west wall south, LSB the south-most one * * connector 3: west wall north, LSB the south-most one * Target: - * * [0]: 00000000 00nnnsss (east wall) - * * [1]: 00000000 00nnnsss (west wall) + * * 00000000 00sssnnn (both east and wall) */ // connector (32 bit): 00000SSS LVDSLVDS LVDSLVDS LVDSLVDS @@ -1038,12 +1132,11 @@ namespace daq constexpr std::size_t FirstSectorBit = 27; constexpr std::size_t SectorBitsPerConnector = 3; - // TODO check the order of the bits: [ ] the blocks of 3 and [ ] within each block return static_cast( - (bits(connectorWord) << SectorBitsPerConnector) - | (bits(connectorWord) ) + (bits(connectorWord) << SectorBitsPerConnector) + | (bits(connectorWord)) ); - } // TriggerDecoderV3::encodeSectorBits() + } // TriggerDecoderV3::encodeSectorBitsLegacy() sim::BeamType_t TriggerDecoderV3::simGateType(sbn::triggerSource source) diff --git a/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx index cf99c18c2..b59a92fc1 100644 --- a/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx +++ b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.cxx @@ -21,7 +21,6 @@ #include "cetlib_except/exception.h" // C/C++ standard libraries -#include #include // std::sort() #include // std::dec, std::hex #include // std::div() @@ -29,31 +28,15 @@ // ----------------------------------------------------------------------------- -// --- icarus::trigger::LVDSbitID implementation +// --- icarus::trigger::PMTpairBitID implementation // ----------------------------------------------------------------------------- std::ostream& icarus::trigger::operator<< - (std::ostream& out, LVDSbitID const& bit) + (std::ostream& out, PMTpairBitID const& bit) { - static constexpr char Side[] = "EW"; - auto const fillch = out.fill(); // width is reset to 0, fill character is not - out << (bit.hasCryostat()? Side[bit.cryostat]: '?') - << '/' << (bit.hasPMTwall()? Side[bit.PMTwall]: '?') - << '/' << std::setw(2) << std::setfill('0') << bit.statusBit - << std::setfill(fillch); - return out; + return out << static_cast(bit); } -// ----------------------------------------------------------------------------- -auto icarus::trigger::LVDSbitID::fromIndex(Index_t index) -> LVDSbitID -{ - auto const [ cryostat, wb ] - = std::div(index, NPMTwallsPerCryostat * NStatusWordBits); - auto const [ wall, bit ] = std::div(wb, NStatusWordBits); - return { (std::size_t) cryostat, (std::size_t) wall, (StatusBit_t) bit }; -} // icarus::trigger::LVDSbitID::fromIndex() - - // ----------------------------------------------------------------------------- // --- icarus::trigger::LVDSHWbitID implementation // ----------------------------------------------------------------------------- @@ -81,18 +64,39 @@ auto icarus::trigger::LVDSHWbitID::fromIndex(Index_t index) -> LVDSHWbitID { // ----------------------------------------------------------------------------- -// --- icarus::trigger::PMTpairBitMap implementation +// --- icarus::trigger::LVDSbitMaps implementation // ----------------------------------------------------------------------------- -auto icarus::trigger::PMTpairBitMap::bitSource(LVDSbitID bit) const +bool icarus::trigger::LVDSbitMaps::hasMap(Map map) const { + switch (map) { + case Map::PMTpairs: return !fLVDSinfoMap.empty(); + case Map::Adders: return !fAdderInfoMap.empty(); + default: + throw Exception{ Errors::Error } + << "Logic error: hasMap() does not support map type #" + << static_cast(map) << "!\n"; + } // switch +} // icarus::trigger::LVDSbitMaps::hasMap() + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::bitSource(PMTpairBitID const& bit) const -> LVDSinfo_t { return fLVDSinfoMap.at(bit.index()); } +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::bitSource(AdderBitID const& bit) const + -> AdderInfo_t +{ + return fAdderInfoMap.at(bit.index()); +} + + // ----------------------------------------------------------------------------- std::vector> -icarus::trigger::PMTpairBitMap::channelPairs() const { +icarus::trigger::LVDSbitMaps::channelPairs() const { std::vector> pairs; for (LVDSinfo_t const& info: fLVDSinfoMap) { @@ -102,18 +106,46 @@ icarus::trigger::PMTpairBitMap::channelPairs() const { return pairs; } + +// ----------------------------------------------------------------------------- +void icarus::trigger::LVDSbitMaps::buildAllMaps + (icarusDB::IICARUSChannelMapProvider const& channelMap) +{ + buildMaps(channelMap, { Map::PMTpairs, Map::Adders }); +} + + +// ----------------------------------------------------------------------------- +void icarus::trigger::LVDSbitMaps::buildMaps( + icarusDB::IICARUSChannelMapProvider const& channelMap, + std::initializer_list maps +) { + + fPMTchannelInfo = buildChannelInfo(channelMap); + + for (Map map: maps) { + switch (map) { + case Map::PMTpairs: + fLVDSinfoMap = buildPMTpairSourceMap(channelMap); + break; + case Map::Adders: + fAdderInfoMap = buildAdderSourceMap(channelMap); + break; + } // switch + } // for + +} // icarus::trigger::LVDSbitMaps::buildMaps() + + // ----------------------------------------------------------------------------- -auto icarus::trigger::PMTpairBitMap::buildLVDSsourceMap +auto icarus::trigger::LVDSbitMaps::buildChannelInfo (icarusDB::IICARUSChannelMapProvider const& channelMap) const - -> std::vector + -> std::vector { - // - // get the LVDS connector/bit information channel by channel - // std::vector PMTchannelDB; - mf::LogTrace debugLog{ "PMTpairBitMap" }; - debugLog << "PMTpairBitMap::buildLVDSsourceMap() collecting channel info:"; + mf::LogTrace debugLog{ "LVDSbitMaps" }; + debugLog << "LVDSbitMaps::buildChannelInfo() collecting channel info:"; for (unsigned int const fragmentID: util::counter(0x2000, 0x2018)) { if (!channelMap.hasPMTDigitizerID(fragmentID)) { @@ -127,22 +159,22 @@ auto icarus::trigger::PMTpairBitMap::buildLVDSsourceMap ) { unsigned short int const cryostat = info.channelID / NChannelsPerCryostat; - if (!info.hasLVDSinfo()) { - throw Exception{ Errors::NoPairBitInformation } - << "PMT fragment ID 0x" << std::hex << fragmentID << std::dec - << " has no LVDS information in the channel mapping database.\n"; - } - debugLog << "\n[#" << PMTchannelDB.size() << "] CH=" << info.channelID << " " << cryostat << "/" << info.LVDSconnector << "/" << info.LVDSbit; + LVDSHWbitID const PMTpairBit = info.hasLVDSinfo() + ? LVDSHWbitID{ cryostat, info.LVDSconnector, info.LVDSbit } + : LVDSHWbitID{} + ; + LVDSHWbitID const adderBit = info.hasAdderInfo() + ? LVDSHWbitID{ cryostat, info.adderConnector, info.adderBit } + : LVDSHWbitID{} + ; + PMTchannelDB.push_back(LVDSchannel_t{ info.channelID // .channel - , LVDSHWbitID{ // .LVDSsource - cryostat // .cryostat - , info.LVDSconnector // .connector - , info.LVDSbit // .bit - } + , PMTpairBit // .PMTpairSource + , adderBit // .adderSource }); } // for channel in fragment } // for fragment ID @@ -151,9 +183,21 @@ auto icarus::trigger::PMTpairBitMap::buildLVDSsourceMap std::sort(PMTchannelDB.begin(), PMTchannelDB.end()); - // - // fill the logic LVDS bit positions with their source information - // + return PMTchannelDB; + +} // icarus::trigger::LVDSbitMaps::buildChannelInfo() + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::buildPMTpairSourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const + -> std::vector +{ + // note that this function must support also the case where there is no PMT + // pair information in the channel mapping database, in which case all + // connector information will be "invalid" and protocol demands an empty map + + assert(fPMTchannelInfo.size() == NChannels); // insider knowledge... /* * The logic of the filling is a bit complicated. Class documentation explains @@ -174,27 +218,29 @@ auto icarus::trigger::PMTpairBitMap::buildLVDSsourceMap * track the channels we already covered when processing the other PMT * channel of the pair, and skip them when met those LVDS bit again * * the destination bit is also an ordered progression within the word, - * but inverted: note in fact that if we inverted the position of the bits - * in each 32-bit word + * but inverted because the _most_ significant bit is assigned to the + * _lowest_ channels */ - std::vector sourceMap{ LVDSbitID::NIndices() }; - std::vector HWtoLogicID{ LVDSHWbitID::NIndices() }; + std::vector sourceMap{ PMTpairBitID::NIndices() }; + std::vector HWtoLogicID{ LVDSHWbitID::NIndices() }; unsigned int nBits = 0; - for (LVDSchannel_t const& info: PMTchannelDB) { // in PMT channel order + for (LVDSchannel_t const& info: fPMTchannelInfo) { // in PMT channel order + + if (!info.PMTpairSource) continue; // skip invalid - LVDSHWbitID::Index_t const sourceIndex = info.LVDSsource.index(); + LVDSHWbitID::Index_t const sourceIndex = info.PMTpairSource.index(); - LVDSbitID logicBit = HWtoLogicID[sourceIndex]; + PMTpairBitID logicBit = HWtoLogicID[sourceIndex]; LVDSinfo_t* mapInfo = logicBit? &(sourceMap[logicBit.index()]): nullptr; if (!mapInfo) { // new bit: set the source // which logic bit to set: - logicBit.cryostat = info.LVDSsource.cryostat; + logicBit.cryostat = info.PMTpairSource.cryostat; assert(logicBit.cryostat == info.channel / NChannelsPerCryostat); logicBit.PMTwall - = info.channel / (NChannelsPerCryostat/LVDSbitID::NPMTwallsPerCryostat) - % LVDSbitID::NCryostats; + = info.channel/(NChannelsPerCryostat/PMTpairBitID::NPMTwallsPerCryostat) + % PMTpairBitID::NCryostats; logicBit.statusBit = 47 - (nBits++ % 48); // 0 => 47, 1 => 46,..., 47 => 0 if (logicBit.statusBit >= 24) logicBit.statusBit += 8; // 0 => 0,..., 23 => 23, 24 => 32, 25 => 33,... @@ -202,13 +248,115 @@ auto icarus::trigger::PMTpairBitMap::buildLVDSsourceMap assert(logicBit); HWtoLogicID[sourceIndex] = logicBit; mapInfo = &(sourceMap[logicBit.index()]); - mapInfo->source = info.LVDSsource; + mapInfo->ID = logicBit; + mapInfo->source = info.PMTpairSource; + } + assert(mapInfo); + assert(mapInfo->source == info.PMTpairSource); + + mapInfo->channels.push_back(info.channel); + // check that they are sorted (the input list should be sorted already...) + assert((mapInfo->channels.size() == 1) + || ( + mapInfo->channels[mapInfo->channels.size()-2] + < mapInfo->channels[mapInfo->channels.size()-1] + )); + + } // for fPMTchannelInfo + + + // --- BEGIN DEBUG ----- + mf::LogTrace debugLog{ "LVDSbitMaps" }; + debugLog << "Final PMT pair bit map:"; + for (auto const& [ bit, info ]: util::enumerate(sourceMap)) { + debugLog << "\n[" << std::setw(3) << bit << "] logic=" + << PMTpairBitID::fromIndex(bit) << " => HW="; + if (info.source) { + debugLog << info.source << " (ix=" + << std::setw(3) << info.source.index() << ") " + << info.channels.size() << " channels {"; + for (raw::Channel_t const channel: info.channels) + debugLog << " " << std::setw(3) << channel; + debugLog << " }"; + } + else { + debugLog << "(invalid)"; + } + } + // --- END DEBUG ------- + + assert((nBits == 0) || (nBits == 192)); // 0 if no info in mapping database + return (nBits == 0)? std::vector{}: sourceMap; + +} // icarus::trigger::LVDSbitMaps::buildPMTpairSourceMap() + + +// ----------------------------------------------------------------------------- +auto icarus::trigger::LVDSbitMaps::buildAdderSourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const + -> std::vector +{ + // note that this function must support also the case where there is no adder + // information in the channel mapping database, in which case all connector + // information will be "invalid" and the protocol demands an empty map + + assert(fPMTchannelInfo.size() == NChannels); // insider knowledge... + + /* + * The logic of the filling is a bit complicated. Class documentation explains + * it and shows it to be: + * + * east cryostat, east wall: `0x0a000000'0A000000`, channel `0` the most + * significant bit in `a` (bit #58) and channel `45` the one in `A` (bit 26); + * east cryostat, west wall: `0x0a000000'0A000000`, channel `90` the most + * significant bit in `a` (bit #58) and channel `135` the one in `d` (bit 26); + * west cryostat, east wall: `0x0a000000'0A000000`, channel `180` the most + * significant bit in `a` (bit #58) and channel `225` the one in `d` (bit 26); + * west cryostat, west wall: `0x0a000000'0A000000`, channel `270` the most + * significant bit in `a` (bit #58) and channel `315` the one in `d` (bit 26). + * + * How we get there: + * * we sort the filling with an ordered progression of channels, lowest + * first, and because adder bit are associated to 15 channels, we + * track the channels we already covered when processing the other PMT + * channels of the adder, and skip them when met those LVDS bit again + * * the destination bit is also an ordered progression within the word, + * but inverted because the _most_ significant bit is assigned to the + * _lowest_ channels + */ + std::vector sourceMap{ AdderBitID::NIndices() }; + std::vector HWtoLogicID{ LVDSHWbitID::NIndices() }; + + unsigned int nBits = 0; + for (LVDSchannel_t const& info: fPMTchannelInfo) { // in PMT channel order + + if (!info.adderSource) continue; // skip invalid + + LVDSHWbitID::Index_t const sourceIndex = info.adderSource.index(); + + AdderBitID logicBit = HWtoLogicID[sourceIndex]; + + AdderInfo_t* mapInfo = logicBit? &(sourceMap[logicBit.index()]): nullptr; + if (!mapInfo) { // new bit: set the source + // which logic bit to set: + logicBit.cryostat = info.adderSource.cryostat; + assert(logicBit.cryostat == info.channel / NChannelsPerCryostat); + logicBit.PMTwall + = info.channel / (NChannelsPerCryostat/AdderBitID::NPMTwallsPerCryostat) + % AdderBitID::NCryostats; + logicBit.statusBit = 5 - (nBits++ % 6); // 0/6/... => 5, 1/7/... => 4, ... + + assert(logicBit); + HWtoLogicID[sourceIndex] = logicBit; + mapInfo = &(sourceMap[logicBit.index()]); + mapInfo->ID = logicBit; + mapInfo->source = info.adderSource; } assert(mapInfo); - assert(mapInfo->source == info.LVDSsource); + assert(mapInfo->source == info.adderSource); mapInfo->channels.push_back(info.channel); - // check that they are sorted (the input list should be sorted already..._ + // check that they are sorted (the input list should be sorted already...) assert((mapInfo->channels.size() == 1) || ( mapInfo->channels[mapInfo->channels.size()-2] @@ -219,10 +367,11 @@ auto icarus::trigger::PMTpairBitMap::buildLVDSsourceMap // --- BEGIN DEBUG ----- - debugLog << "\nFinal map:"; + mf::LogTrace debugLog{ "LVDSbitMaps" }; + debugLog << "Final adder bit map:"; for (auto const& [ bit, info ]: util::enumerate(sourceMap)) { - debugLog << "\n[ " << std::setw(3) << bit << "] logic=" - << LVDSbitID::fromIndex(bit) << " => HW="; + debugLog << "\n[" << std::setw(3) << bit << "] logic=" + << AdderBitID::fromIndex(bit) << " => HW="; if (info.source) { debugLog << info.source << " (ix=" << std::setw(3) << info.source.index() << ") " @@ -237,14 +386,14 @@ auto icarus::trigger::PMTpairBitMap::buildLVDSsourceMap } // --- END DEBUG ------- - assert(nBits == 192); - return sourceMap; + assert((nBits == 0) || (nBits == 24)); // 0 if no info in mapping database + return (nBits == 0)? std::vector{}: sourceMap; -} // icarus::trigger::PMTpairBitMap::buildLVDSsourceMap() +} // icarus::trigger::LVDSbitMaps::buildAdderSourceMap() // ----------------------------------------------------------------------------- -std::string icarus::trigger::PMTpairBitMap::errorMessage(Errors code) { +std::string icarus::trigger::LVDSbitMaps::errorMessage(Errors code) { using namespace std::string_literals; switch (code) { case Errors::NoError: @@ -257,12 +406,14 @@ std::string icarus::trigger::PMTpairBitMap::errorMessage(Errors code) { return "channel mapping database does not have PMT fragment information"s; case Errors::NoPairBitInformation: return "channel mapping database does not have PMT pair bit information"s; + case Errors::NoAdderBitInformation: + return "channel mapping database does not have adder bit information"s; case Errors::Unknown: return "unknown error"s; default: return "unexpected error code "s + std::to_string(static_cast(code)); } // switch -} // icarus::trigger::PMTpairBitMap::errorMessage() +} // icarus::trigger::LVDSbitMaps::errorMessage() // ----------------------------------------------------------------------------- diff --git a/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h index 3b6dae93f..d074fae4a 100644 --- a/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h +++ b/icaruscode/PMT/Trigger/Algorithms/LVDSbitMaps.h @@ -15,7 +15,9 @@ #include "cetlib_except/coded_exception.h" // C/C++ standard libraries -#include // std::ostream +#include +#include // std::setw(), std::setfill() +#include #include #include #include @@ -25,13 +27,15 @@ // forward declarations namespace icarus::trigger { - struct LVDSbitID; + struct PMTpairBitID; + struct AdderBitID; struct LVDSHWbitID; struct LVDSinfo_t; + struct AdderInfo_t; - class PMTpairBitMap; + class LVDSbitMaps; - std::ostream& operator<< (std::ostream& out, LVDSbitID const& bit); + std::ostream& operator<< (std::ostream& out, PMTpairBitID const& bit); std::ostream& operator<< (std::ostream& out, LVDSHWbitID const& bit); } // namespace icarus::trigger @@ -40,12 +44,20 @@ namespace icarusDB { class IICARUSChannelMapProvider; } // ----------------------------------------------------------------------------- -/// Identifier of a "logical" LVDS bit +/// Identifier of a "logical" LVDS bit for PMT pairs /// following `sbn::ExtraTriggerInfo` convention. -struct icarus::trigger::LVDSbitID { - +namespace icarus::trigger::details { + template struct PMTwallBitID; + template + std::ostream& operator<< + (std::ostream& out, PMTwallBitID const& bit); +} +template +struct icarus::trigger::details::PMTwallBitID { + + using BitID_t = PMTwallBitID; ///< This ID type. using StatusBit_t = unsigned short int; ///< Type for bit in the status word. - using Index_t = unsigned short int; ///< Type for LVDS bit ID index. + using Index_t = unsigned short int; ///< Type for bit ID index. /// How many cryostats in the detector. static constexpr std::size_t NCryostats = 2; @@ -54,7 +66,7 @@ struct icarus::trigger::LVDSbitID { static constexpr std::size_t NPMTwallsPerCryostat = 2; /// How many bits in a status word. - static constexpr unsigned short int NStatusWordBits = 64; + static constexpr unsigned short int NStatusWordBits = MaxBits; /// Special value to identify the absence of cryostat information. @@ -113,13 +125,41 @@ struct icarus::trigger::LVDSbitID { /// Rebuilds an ID object from its index. - static LVDSbitID fromIndex(Index_t index); + static constexpr BitID_t fromIndex(Index_t index); /// Returns the number of possible indices (`0` to `NIndices() - 1`). static constexpr Index_t NIndices() { return NCryostats * NPMTwallsPerCryostat * NStatusWordBits; } -}; // icarus::trigger::LVDSbitID +}; // icarus::trigger::details::PMTwallBitID + + +// ----------------------------------------------------------------------------- +/// Identifier of a "logical" LVDS bit for PMT pairs +/// following `sbn::ExtraTriggerInfo` convention. +struct icarus::trigger::PMTpairBitID: public details::PMTwallBitID<64> { + + using BaseID_t = details::PMTwallBitID<64>; + + /// Rebuilds an ID object from its index. + static PMTpairBitID fromIndex(Index_t index) + { return { BaseID_t::fromIndex(index) }; } + +}; // icarus::trigger::PMTpairBitID + + +// ----------------------------------------------------------------------------- +/// Identifier of a "logical" LVDS bit for adders +/// following `sbn::ExtraTriggerInfo` convention. +struct icarus::trigger::AdderBitID: public details::PMTwallBitID<16> { + + using BaseID_t = details::PMTwallBitID<16>; + + /// Rebuilds an ID object from its index. + static AdderBitID fromIndex(Index_t index) + { return { BaseID_t::fromIndex(index) }; } + +}; // icarus::trigger::AdderBitID // ----------------------------------------------------------------------------- @@ -132,7 +172,7 @@ struct icarus::trigger::LVDSHWbitID { using Index_t = unsigned short int; ///< Type for LVDS bit ID index. /// How many cryostats in the detector. - static constexpr unsigned short int NCryostats = LVDSbitID::NCryostats; + static constexpr unsigned short int NCryostats = PMTpairBitID::NCryostats; /// How many 32-bit connectors come out of each cryostat. static constexpr unsigned short int NConnectorsPerCryostat = 4; @@ -198,10 +238,11 @@ struct icarus::trigger::LVDSHWbitID { // ----------------------------------------------------------------------------- -/// Description of a (logical) LVDS bit and additional information -/// (none so far). +/// Information about a PMT pair: source bit and connected channels. struct icarus::trigger::LVDSinfo_t { + PMTpairBitID ID; ///< Identifier of this bit. + LVDSHWbitID source; ///< Identifier of the hardware LVDS bit feeding this one. std::vector channels; ///< PMT channels covered by this bit. @@ -209,13 +250,31 @@ struct icarus::trigger::LVDSinfo_t { /// Returns whether this information is fully valid (both channel and ID). operator bool() const { return source; } - /// Order operator: sorted after the LVDS index. - bool operator< (LVDSinfo_t const& other) const - { return source.index() < other.source.index(); } + /// Order operator: sorted after the ID. + bool operator< (LVDSinfo_t const& other) const { return ID < other.ID; } }; // icarus::trigger::LVDSinfo_t +// ----------------------------------------------------------------------------- +/// Description of a (logical) adder bit and additional information. +struct icarus::trigger::AdderInfo_t { + + AdderBitID ID; ///< Identifier of this bit. + + LVDSHWbitID source; ///< Identifier of the hardware LVDS bit feeding this one. + + std::vector channels; ///< PMT channels covered by this bit. + + /// Returns whether this information is fully valid (both channel and ID). + operator bool() const { return source; } + + /// Order operator: sorted after the source index. + bool operator< (AdderInfo_t const& other) const { return ID < other.ID; } + +}; // icarus::trigger::AdderInfo_t + + // ----------------------------------------------------------------------------- /** * @brief Utility to build and maintain ICARUS trigger LVDS bit maps. @@ -236,13 +295,20 @@ struct icarus::trigger::LVDSinfo_t { * -------------------- * * The logic mapping is described in `sbn::ExtraTriggerInfo::LVDSstatus`. - * Compared with that mapping, the LVDS bit ID are assigned so that + * Compared with that mapping, the PMT pair bit ID are assigned so that * * the index of the ID, modulo 64 bits, matches the `LVDSstatus` words; * * cryostats and PMT walls of the IDs are honoured, so that they can be used - * to directly adderss `LVDSstatus`. + * to directly address `LVDSstatus`. * - * This results in the following mapping, "first bit" meaning the most significant: + * Likewise, the adder bit ID are assigned so that + * * the index of the ID, modulo 6 bits, matches the `sectorStatus` words; + * * cryostats and PMT walls of the IDs are honoured, so that they can be used + * to directly address `sectorStatus`. + * + * This results in the following mapping, "first bit" meaning the most + * significant. * + * PMT pairs: * * east cryostat, east wall: `0x00aAbBcC'00dDeEfF`, channel `0` the most * significant bit in `a` (bit #55) and channel `45` the one in `d` (bit 23); * * east cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `90` the most @@ -252,6 +318,16 @@ struct icarus::trigger::LVDSinfo_t { * * west cryostat, west wall: `0x00aAbBcC'00dDeEfF`, channel `270` the most * significant bit in `a` (bit #55) and channel `315` the one in `d` (bit 23). * + * Adders: + * * east cryostat, east wall: `0b00000000'00abcdef`, with `a` to `f` covering + * adders starting with channel `0`, `15`, 30`, `45`, 60` and `75`; + * * east cryostat, west wall: `0b00000000'00abcdef`, with `a` to `f` covering + * adders starting with channel `90`, `105`, 120`, `135`, 150` and `165`; + * * west cryostat, east wall: `0b00000000'00abcdef`, with `a` to `f` covering + * adders starting with channel `180`, `195`, 210`, `225`, 240` and `255`; + * * west cryostat, west wall: `0b00000000'00abcdef`, with `a` to `f` covering + * adders starting with channel `270`, `285`, 300`, `315`, 330` and `345`. + * * * Naming conventions * ------------------- @@ -273,7 +349,7 @@ struct icarus::trigger::LVDSinfo_t { * the PMT feeding the bit. This is equivalent to the logical PMT channel * number described above, and here we call it a "logical LVDS bit ID". * Analyzers will typically deal with this logical ID only. It is described by - * `icarus::trigger::LVDSbitID` data type. + * `icarus::trigger::PMTpairBitID` data type. * * The assembly of the bits in hardware includes the combination of 8-bit * information from groups of three PMT readout boards, physically using LVDS * format and for that often just dubbed "the LVDS signals", via custom LVDS @@ -285,7 +361,7 @@ struct icarus::trigger::LVDSinfo_t { * PMT channels is encoded in the hardware database; analyzers typically don't * have to deal with them, because the trigger decoding software will * translate them into the `sbn::ExtraTriggerInfo` convention (using, in fact, - * this `PMTpairBitMap` object for the mapping). The data type devoted to this + * this `LVDSbitMaps` object for the mapping). The data type devoted to this * concept is `LVDSHWbitID`. Note that the NIM FPGA boards combine two 32-bit * connector words into 64-bit words for transportation. These 64-bit words * are stored in the trigger DAQ fragment. At first look, these 64-bit words @@ -297,7 +373,7 @@ struct icarus::trigger::LVDSinfo_t { * as the most significant 32-bit word, 1 and 3 as the least significant one). * */ -class icarus::trigger::PMTpairBitMap { +class icarus::trigger::LVDSbitMaps { public: @@ -319,12 +395,19 @@ class icarus::trigger::PMTpairBitMap { /// Error codes from this object. See `errorMessage()` code for descriptions. enum class Errors { - NoError, ///< No error condition. - Error, ///< An unspecified error condition. - NoDatabase, ///< No channel mapping database access. - NoPMTfragment, ///< Missing a required PMT fragment information. - NoPairBitInformation, ///< No PMT pair bit information in the database. - Unknown ///< Unknown error. + NoError, ///< No error condition. + Error, ///< An unspecified error condition. + NoDatabase, ///< No channel mapping database access. + NoPMTfragment, ///< Missing a required PMT fragment information. + NoPairBitInformation, ///< No PMT pair bit information in the database. + NoAdderBitInformation, ///< No adder bit information in the database. + Unknown ///< Unknown error. + }; + + /// Supported maps. + enum class Map { + PMTpairs, ///< Map of PMT pairs ("LVDS"). + Adders, ///< Map of adder signals. }; /// Returns a predefined message describing the specified error `code`. @@ -334,20 +417,31 @@ class icarus::trigger::PMTpairBitMap { using Exception = cet::coded_exception; /// Default constructor: maps empty until `rebuild()` is called. - PMTpairBitMap() = default; + LVDSbitMaps() = default; /// Constructor: builds all the maps based on the specified PMT channel map. - PMTpairBitMap(icarusDB::IICARUSChannelMapProvider const& channelMap) + LVDSbitMaps(icarusDB::IICARUSChannelMapProvider const& channelMap) { buildAllMaps(channelMap); } + /// Constructor: builds the specified maps based on the specified PMT channel + /// map. + LVDSbitMaps( + icarusDB::IICARUSChannelMapProvider const& channelMap, + std::initializer_list maps + ) + { buildMaps(channelMap, std::move(maps)); } + // --- BEGIN ----- Query interface ----------------------------------------- /// @name Query interface. /// @{ + /// Returns whether the specified map is present. + bool hasMap(Map map) const; + /** - * @brief Returns the source bit for the specified encoded bit. - * @param bit ID of the logical LVDS bit to get the source information of + * @brief Returns the source bit for the specified encoded PMT pair bit. + * @param bit ID of the logical PMT pair bit to get the source information of * @return the number of hardware connector and bit where to find the value * @throws std::out_of_range if the specified bit is not in the map * @@ -358,11 +452,33 @@ class icarus::trigger::PMTpairBitMap { * The returned information includes: * * the cryostat the bit comes from (the same as the one in `bit`); * * the hardware connector number the bit comes from; - * * the hardware bit (wire) in the connector which carries the `bit` value. + * * the hardware bit (wire) in the connector which carries the `bit` value; + * * the PMT channels covered by the bit. + * + * See `buildPMTpairSourceMap()` for details of the mapping definitions. + */ + LVDSinfo_t bitSource(PMTpairBitID const& bit) const; + + + /** + * @brief Returns the source bit for the specified encoded adder bit. + * @param bit ID of the logical adder bit to get the source information of + * @return the number of hardware connector and bit where to find the value + * @throws std::out_of_range if the specified bit is not in the map + * + * The output `connector`/`bit` bits are sorted according to the prescription + * of `sbn::ExtraTriggerInfo`, which requires the least significant bit to + * have the most upstream adder signal (i.e. the lowest channels). + * + * The returned information includes: + * * the cryostat the bit comes from (the same as the one in `bit`); + * * the hardware connector number the bit comes from; + * * the hardware bit (wire) in the connector which carries the `bit` value; + * * the PMT channels covered by the bit. * - * See `buildLVDSsourceMap()` for details of the mapping definitions. + * See `buildAdderSourceMap()` for details of the mapping definitions. */ - LVDSinfo_t bitSource(LVDSbitID bit) const; + AdderInfo_t bitSource(AdderBitID const& bit) const; /** @@ -402,47 +518,110 @@ class icarus::trigger::PMTpairBitMap { raw::Channel_t channel = NoChannel; ///< ID of an associated PMT channel. - LVDSHWbitID LVDSsource; ///< Identifier of the LVDS bit source. + LVDSHWbitID PMTpairSource; ///< Identifier of the PMT pair bit source. + LVDSHWbitID adderSource; ///< Identifier of the adder bit source. /// Returns an "unique" index for the LVDS source. - constexpr unsigned short int index() const { return LVDSsource.index(); } + constexpr unsigned short int index() const { return PMTpairSource.index(); } /// Returns whether this information is fully valid (both channel and ID). - operator bool() const { return (channel != NoChannel) && LVDSsource; } + operator bool() const + { return (channel != NoChannel) && PMTpairSource && adderSource; } /// Order operator: sorted after the channel ID only. bool operator< (LVDSchannel_t const& other) const { return channel < other.channel; } }; // LVDSchannel_t - - - /// For each logic LVDS bit index, the HW LVDS ID and information are stored. + + + /// Cached information of each PMT (sorted by PMT channel). + std::vector fPMTchannelInfo; + + /// For each PMT pair bit index, the HW LVDS ID and information are stored. std::vector fLVDSinfoMap; + /// For each adder bit ID, adder information is stored. + std::vector fAdderInfoMap; + /// Builds all the maps - void buildAllMaps(icarusDB::IICARUSChannelMapProvider const& channelMap) - { - fLVDSinfoMap = buildLVDSsourceMap(channelMap); - } + void buildAllMaps(icarusDB::IICARUSChannelMapProvider const& channelMap); + + /// Builds the specified maps. If any error occurs, an `Exception` is thrown. + void buildMaps( + icarusDB::IICARUSChannelMapProvider const& channelMap, + std::initializer_list maps + ); + + /** + * @brief Fills a list of PMT channels and their relevant information + * @param channelMap service provider to associate each PMT channel to LVDS + * @return the list, sorted by PMT channel ID + */ + std::vector buildChannelInfo + (icarusDB::IICARUSChannelMapProvider const& channelMap) const; /** * @brief Builds the LVDS map connecting each bit to its hardware source. * @param channelMap service provider to associate each PMT channel to LVDS * @return a map * - * The map is multi-level. The first level is the cryostat index (up to - * `NCryostats - 1`), then for each cryostat a vector hosts in each index - * the information of the LVDS ID matching that index. - * The information includes the source in the hardware connector of the bit. - * + * The map is associating the unique PMT pair bit ID (`PMTpairBitID::index()`) + * and the information of the LVDS ID matching that ID. + * The information includes the source in the hardware connector of the bit + * and the involved channels (see `LVDSinfo_t` for more). */ - std::vector buildLVDSsourceMap + std::vector buildPMTpairSourceMap (icarusDB::IICARUSChannelMapProvider const& channelMap) const; + /** + * @brief Builds the adder map connecting each bit to its hardware source. + * @param channelMap service provider to associate each PMT channel to LVDS + * @return a map + * + * The map is associating the unique adder bit ID (`AdderBitID::index()`) + * and the information of the hardware LVDS ID matching that ID. + * The information includes the source in the hardware connector of the bit + * and the involved channels (see `AdderInfo_t` for more). + * + */ + std::vector buildAdderSourceMap + (icarusDB::IICARUSChannelMapProvider const& channelMap) const; + -}; // icarus::trigger::PMTpairBitMap +}; // icarus::trigger::LVDSbitMaps + + +// ----------------------------------------------------------------------------- +// --- template implementation +// ----------------------------------------------------------------------------- +// --- icarus::trigger::PMTwallBitID +// ----------------------------------------------------------------------------- +template +constexpr auto icarus::trigger::details::PMTwallBitID::fromIndex + (Index_t index) -> BitID_t +{ + auto const [ cryostat, wb ] + = std::div(index, NPMTwallsPerCryostat * NStatusWordBits); + auto const [ wall, bit ] = std::div(wb, NStatusWordBits); + return { (std::size_t) cryostat, (std::size_t) wall, (StatusBit_t) bit }; +} // icarus::trigger::PMTwallBitID<>::fromIndex() + + +// ----------------------------------------------------------------------------- +template +std::ostream& icarus::trigger::details::operator<< + (std::ostream& out, PMTwallBitID const& bit) +{ + static constexpr char Side[] = "EW"; + auto const fillch = out.fill(); // width is reset to 0, fill character is not + out << (bit.hasCryostat()? Side[bit.cryostat]: '?') + << '/' << (bit.hasPMTwall()? Side[bit.PMTwall]: '?') + << '/' << std::setw(2) << std::setfill('0') << bit.statusBit + << std::setfill(fillch); + return out; +} // ----------------------------------------------------------------------------- From 5327000ee92ca15916295b4379d7f419de1a65fb Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Tue, 19 Mar 2024 18:55:07 -0500 Subject: [PATCH 10/12] Trigger decoder: fixed condition for trigger from both cryostats It has been found that the FPGS logic assigning a trigger to adder or majority logic may, in case the decision is "both", miss one bit, hence returning a value 3 or 5 instead of the canonical 7. Although this has not been observed in the triggering cryostat information, I am relaxing the decoding logic so that in those cases the trigger is assigned to both cryostats. --- icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc index fc8ea25ea..01bf11e62 100644 --- a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc +++ b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc @@ -786,7 +786,7 @@ namespace daq locationMask = mask(sbn::triggerLocation::CryoEast); else if(triggerLocation == 2) locationMask = mask(sbn::triggerLocation::CryoWest); - else if(triggerLocation == 7) + else if(triggerLocation >= 3) // should be 7 locationMask = mask(sbn::triggerLocation::CryoEast, sbn::triggerLocation::CryoWest); fTriggerExtra->triggerLocationBits = locationMask; From 49324196115a950fd8ad4fdcfcc41df8ad685376 Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Tue, 19 Mar 2024 18:59:39 -0500 Subject: [PATCH 11/12] TriggerDecoder decodes the triggering logic of an event That can be majority (PMT pair) trigger, adder trigger or both. This information is stored independently for each cryostat (in case both cryostats are reported as triggering). --- .../DecoderTools/TriggerDecoderV3_tool.cc | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc index 01bf11e62..a53e2cf2b 100644 --- a/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc +++ b/icaruscode/Decode/DecoderTools/TriggerDecoderV3_tool.cc @@ -165,6 +165,10 @@ namespace daq * the trigger window; only the first one becomes the global * trigger, but we still keep the count of how many happen. * Its value is `0` when trigger happened from elsewhere. + * * `triggerLogicBits`: whether the trigger on this cryostat was from + * adder logic, trigger logic or both. The definition of "both" + * is taken by the hardware, based on an allowed overlapping time + * between the signals of the different logics, properly delayed. * * `LVDSstatus`: information per PMT wall (i.e. TPC; east first, then * west) of the LVDS signals of the discriminated PMT pairs at the * time of the global trigger. All bits are `0` when trigger @@ -793,24 +797,38 @@ namespace daq // // fill sbn::ExtraTriggerInfo::cryostats // - auto setCryoInfo - = [this,&extra=*fTriggerExtra,isFirstEvent=(triggerID <= 1)] - (std::size_t cryo, icarus::KeyValuesData const& data) + auto setCryoInfo = [ + this,&extra=*fTriggerExtra,isFirstEvent=(triggerID <= 1),data=parsedData + ] + (std::size_t cryo) { - std::string const SIDE = (cryo == sbn::ExtraTriggerInfo::EastCryostat) + std::string const Side + = (cryo == sbn::ExtraTriggerInfo::EastCryostat) ? "EAST": "WEST"; + std::string const CrSide = (cryo == sbn::ExtraTriggerInfo::EastCryostat) ? "Cryo1 EAST": "Cryo2 WEST"; + // trigger logic: 0x01=adders; 0x02=majority; 0x07=both + std::string const triggerLogicKey = "MJ_Adder Source " + Side; + int const triggerLogicCode = data.hasItem(triggerLogicKey) + ? data.getItem(triggerLogicKey).getNumber(0): 0; + sbn::bits::triggerLogicMask triggerLogicMask; + if(triggerLogicCode == 1) + triggerLogicMask = mask(sbn::triggerLogic::PMTAnalogSum); + else if(triggerLogicCode == 2) + triggerLogicMask = mask(sbn::triggerLogic::PMTPairMajority); + else if(triggerLogicCode >= 3) // should be 7 + triggerLogicMask = mask(sbn::triggerLogic::PMTAnalogSum, sbn::triggerLogic::PMTPairMajority); + extra.cryostats[cryo] = unpackPrimitiveBits( cryo, isFirstEvent, - data.getItem(SIDE + " counts").getNumber(0), - data.getItem(SIDE + " Connector 0 and 1").getNumber(0, 16), - data.getItem(SIDE + " Connector 2 and 3").getNumber(0, 16) + data.getItem(CrSide + " counts").getNumber(0), + data.getItem(CrSide + " Connector 0 and 1").getNumber(0, 16), + data.getItem(CrSide + " Connector 2 and 3").getNumber(0, 16), + triggerLogicMask ); }; - if (triggerLocation & 1) - setCryoInfo(sbn::ExtraTriggerInfo::EastCryostat, parsedData); - if (triggerLocation & 2) - setCryoInfo(sbn::ExtraTriggerInfo::WestCryostat, parsedData); + if (triggerLocation & 1) setCryoInfo(sbn::ExtraTriggerInfo::EastCryostat); + if (triggerLocation & 2) setCryoInfo(sbn::ExtraTriggerInfo::WestCryostat); // // absolute time trigger (raw::ExternalTrigger) @@ -894,7 +912,8 @@ namespace daq sbn::ExtraTriggerInfo::CryostatInfo TriggerDecoderV3::unpackPrimitiveBits( std::size_t cryostat, bool firstEvent, unsigned long int counts, - std::uint64_t connectors01, std::uint64_t connectors23 + std::uint64_t connectors01, std::uint64_t connectors23, + sbn::bits::triggerLogicMask triggerLogic ) const { sbn::ExtraTriggerInfo::CryostatInfo cryoInfo; @@ -907,6 +926,8 @@ namespace daq cryoInfo.sectorStatus = encodeSectorBits(cryostat, connectors01, connectors23); + cryoInfo.triggerLogicBits = static_cast(triggerLogic); + return cryoInfo; } // TriggerDecoderV3::unpackPrimitiveBits() From b2b2ce5a3f4f4b3855198d060ab4833a54467eef Mon Sep 17 00:00:00 2001 From: Gianluca Petrillo Date: Wed, 20 Mar 2024 16:49:02 -0500 Subject: [PATCH 12/12] Updated default SQLite channel mapping database The new SQLite database is available from `icarus_data` `v09_84_01`. PostgreSQL database is not updated yet. --- .../Decode/ChannelMapping/channelmapping_icarus.fcl | 2 +- icaruscode/TPC/Compression/producecompressedjob.fcl | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/icaruscode/Decode/ChannelMapping/channelmapping_icarus.fcl b/icaruscode/Decode/ChannelMapping/channelmapping_icarus.fcl index 215f2953e..57d991e2f 100644 --- a/icaruscode/Decode/ChannelMapping/channelmapping_icarus.fcl +++ b/icaruscode/Decode/ChannelMapping/channelmapping_icarus.fcl @@ -14,7 +14,7 @@ ChannelMappingPostGres: { # for icarusDB::ChannelMapSQLite: ChannelMappingSQLite: { - DBFileName: "ChannelMapICARUS_20230829.db" + DBFileName: "ChannelMapICARUS_20240318.db" CalibDBFileName: "crt_gain_reco_data" Tag: @local::ICARUS_Calibration_GlobalTags.crt_gain_reco_data } diff --git a/icaruscode/TPC/Compression/producecompressedjob.fcl b/icaruscode/TPC/Compression/producecompressedjob.fcl index ac584c5a5..b5085e5cc 100644 --- a/icaruscode/TPC/Compression/producecompressedjob.fcl +++ b/icaruscode/TPC/Compression/producecompressedjob.fcl @@ -1,4 +1,5 @@ #include "services_common_icarus.fcl" +#include "channelmapping_icarus.fcl" #include "ProduceCompressed.fcl" @@ -7,16 +8,7 @@ process_name: TestReprocessRaw services: { @table::icarus_basic_services - IICARUSChannelMap: { - ChannelMappingTool: { - CalibDBFileName: "crt_gain_reco_data" - DBFileName: "ChannelMapICARUS_20230829.db" - Tag: "v1r0" - tool_type: "ChannelMapSQLite" - } - DiagnosticOutput: false - service_provider: "ICARUSChannelMap" - } + IICARUSChannelMap: @local::icarus_channelmappinggservice } source: