From 3d2198eba11eed7012301a40192998a4332ceb43 Mon Sep 17 00:00:00 2001 From: manuel Date: Fri, 17 Apr 2026 16:36:06 +0200 Subject: [PATCH] Add DAPHNE ETH peak descriptor support --- CMakeLists.txt | 1 + include/fddetdataformats/DAPHNEEthFrame.hpp | 494 ++++++++++++++++---- pybindsrc/daphneeth.cpp | 217 +++++++-- unittest/DAPHNEEthFrame_test.cxx | 96 ++++ 4 files changed, 677 insertions(+), 131 deletions(-) create mode 100644 unittest/DAPHNEEthFrame_test.cxx diff --git a/CMakeLists.txt b/CMakeLists.txt index 5919634..5b78fe0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,7 @@ daq_add_unit_test(WIB2Frame_test LINK_LIBRARIES fddetdataformats) daq_add_unit_test(WIBFrame_test LINK_LIBRARIES fddetdataformats) daq_add_unit_test(TDE16Frame_test LINK_LIBRARIES fddetdataformats) daq_add_unit_test(DAPHNEFrame_test LINK_LIBRARIES fddetdataformats) +daq_add_unit_test(DAPHNEEthFrame_test LINK_LIBRARIES fddetdataformats) ############################################################################## diff --git a/include/fddetdataformats/DAPHNEEthFrame.hpp b/include/fddetdataformats/DAPHNEEthFrame.hpp index 865f901..e9baac4 100644 --- a/include/fddetdataformats/DAPHNEEthFrame.hpp +++ b/include/fddetdataformats/DAPHNEEthFrame.hpp @@ -1,9 +1,10 @@ /** * @file DAPHNEEthFrame.hpp * - * Contains declaration of DAPHNEEthFrame, a class for accessing raw WIB eth frames, as used in ProtoDUNE-SP-II - * - * The canonical definition of the DAPHNE format is given in EDMS document 2088726: + * Contains declaration of DAPHNEEthFrame, a class for accessing raw DAPHNE + * Ethernet frames. + * + * The canonical definition of the DAPHNE format is given in EDMS document 2088726: * https://edms.cern.ch/document/2088726 * * This is part of the DUNE DAQ Application Framework, copyright 2020. @@ -16,20 +17,22 @@ #include "detdataformats/DAQEthHeader.hpp" -#include // For std::min -#include // For assert() -#include // For uint32_t etc +#include +#include +#include #include #include -#include // For std::out_of_range +#include +#include namespace dunedaq::fddetdataformats { /** - * @brief Class for accessing raw WIB eth frames, as used in ProtoDUNE-II + * @brief Class for accessing raw DAPHNE Ethernet frames. * - * The canonical definition of the WIB format is given in EDMS document 2088713: - * https://edms.cern.ch/document/2088726 + * The on-wire frame uses one 64-bit header word containing trigger metadata, + * followed by six 64-bit words that carry twelve 32-bit peak descriptor words, + * and finally 1024 packed ADC samples for a single channel. */ class DAPHNEEthFrame { @@ -38,8 +41,9 @@ class DAPHNEEthFrame // Preliminaries // =============================================================== - // The definition of the format is in terms of 64-bit words + // The top-level frame format is described in 64-bit words. typedef uint64_t word_t; // NOLINT + typedef uint32_t descriptor_word_t; // NOLINT // Dataframe format version static constexpr uint8_t version = 1; @@ -48,25 +52,150 @@ class DAPHNEEthFrame static constexpr int s_bits_per_word = 8 * sizeof(word_t); static constexpr int s_num_adcs = 1024; static constexpr int s_num_adc_words = s_num_adcs * s_bits_per_adc / s_bits_per_word; + static constexpr int s_peak_descriptor_words = 12; + static constexpr int s_packed_peak_descriptor_words = 6; + + struct PeakDescriptorData + { + // Word 1: peak 0 odd + descriptor_word_t num_subpeaks_0 : 4; + descriptor_word_t reserved_0 : 4; + descriptor_word_t adc_integral_0 : 23; + descriptor_word_t found_0 : 1; + + // Word 2: peak 0 even + descriptor_word_t adc_max_0 : 14; + descriptor_word_t sample_max_0 : 9; + descriptor_word_t samples_over_baseline_0 : 9; + + // Word 3: peak 1 odd + descriptor_word_t num_subpeaks_1 : 4; + descriptor_word_t reserved_1 : 4; + descriptor_word_t adc_integral_1 : 23; + descriptor_word_t found_1 : 1; + + // Word 4: peak 1 even + descriptor_word_t adc_max_1 : 14; + descriptor_word_t sample_max_1 : 9; + descriptor_word_t samples_over_baseline_1 : 9; + + // Word 5: peak 2 odd + descriptor_word_t num_subpeaks_2 : 4; + descriptor_word_t reserved_2 : 4; + descriptor_word_t adc_integral_2 : 23; + descriptor_word_t found_2 : 1; + + // Word 6: peak 2 even + descriptor_word_t adc_max_2 : 14; + descriptor_word_t sample_max_2 : 9; + descriptor_word_t samples_over_baseline_2 : 9; + + // Word 7: peak 3 odd + descriptor_word_t num_subpeaks_3 : 4; + descriptor_word_t reserved_3 : 4; + descriptor_word_t adc_integral_3 : 23; + descriptor_word_t found_3 : 1; + + // Word 8: peak 3 even + descriptor_word_t adc_max_3 : 14; + descriptor_word_t sample_max_3 : 9; + descriptor_word_t samples_over_baseline_3 : 9; + + // Word 9: peak 4 odd + descriptor_word_t num_subpeaks_4 : 4; + descriptor_word_t reserved_4 : 4; + descriptor_word_t adc_integral_4 : 23; + descriptor_word_t found_4 : 1; + + // Word 10: peak 4 even + descriptor_word_t adc_max_4 : 14; + descriptor_word_t sample_max_4 : 9; + descriptor_word_t samples_over_baseline_4 : 9; + + // Word 11: Time_Start fields for indices 0,1,2 and reserved bits [1:0] + descriptor_word_t samples_start_2 : 10; + descriptor_word_t samples_start_1 : 10; + descriptor_word_t samples_start_0 : 10; + descriptor_word_t reserved_5 : 2; + + // Word 12: Time_Start fields for indices 3,4 and reserved bits [11:0] + descriptor_word_t reserved_6 : 12; + descriptor_word_t samples_start_4 : 10; + descriptor_word_t samples_start_3 : 10; + + static constexpr uint8_t max_peaks = 5; + + inline bool is_found(int idx) const; + inline void set_found(uint8_t val, int idx); + + inline uint32_t get_adc_integral(int idx) const; + inline void set_adc_integral(uint32_t val, int idx); + + inline uint8_t get_num_subpeaks(int idx) const; + inline void set_num_subpeaks(uint8_t val, int idx); + + inline uint16_t get_samples_over_baseline(int idx) const; + inline void set_samples_over_baseline(uint16_t val, int idx); + + inline uint16_t get_sample_max(int idx) const; + inline void set_sample_max(uint16_t val, int idx); + + inline uint16_t get_adc_max(int idx) const; + inline void set_adc_max(uint16_t val, int idx); + + inline uint16_t get_sample_start(int idx) const; + inline void set_sample_start(uint16_t val, int idx); + + inline const descriptor_word_t* as_words() const + { + return reinterpret_cast(this); + } + + inline descriptor_word_t* as_words() + { + return reinterpret_cast(this); + } + }; struct Header - { - // word_t w0; + { word_t trig_sample : 14; - word_t rsv_0 : 2; - word_t threshold : 14; - word_t rsv_1 : 2; - word_t baseline : 14; - word_t rsv_2 : 6; - word_t version : 4; - word_t channel : 8; - - word_t w1; - word_t w2; - word_t w3; - word_t w4; - word_t w5; - word_t w6; + word_t rsv_0 : 2; + word_t threshold : 14; + word_t rsv_1 : 2; + word_t baseline : 14; + word_t rsv_2 : 6; + word_t version : 4; + word_t channel : 8; + + PeakDescriptorData peaks_data; + + descriptor_word_t get_baseline() const + { + return static_cast(baseline); + } + + word_t get_packed_peak_word(int idx) const + { + if (idx < 0 || idx >= s_packed_peak_descriptor_words) { + throw std::out_of_range("Packed peak word index out of range (must be 0-5)"); + } + word_t value = 0; + std::memcpy(&value, + reinterpret_cast(&peaks_data) + idx * sizeof(word_t), + sizeof(value)); + return value; + } + + void set_packed_peak_word(word_t value, int idx) + { + if (idx < 0 || idx >= s_packed_peak_descriptor_words) { + throw std::out_of_range("Packed peak word index out of range (must be 0-5)"); + } + std::memcpy(reinterpret_cast(&peaks_data) + idx * sizeof(word_t), + &value, + sizeof(value)); + } }; // =============================================================== @@ -76,98 +205,275 @@ class DAPHNEEthFrame Header header; word_t adc_words[s_num_adc_words]; // NOLINT -// =============================================================== -// Accessors -// =============================================================== + // =============================================================== + // Accessors + // =============================================================== -/** - * @brief Get the ith ADC value in the frame - * - * The ADC words are 14 bits long, stored packed in the data structure. The order is: - * - * - 1024 adc values from one daphne channel - */ -uint16_t -get_adc(int i) const // NOLINT -{ - if (i < 0 || i >= s_num_adcs) - throw std::out_of_range("ADC index out of range"); - - // The index of the first (and sometimes only) word containing the required ADC value - int word_index = s_bits_per_adc * i / s_bits_per_word; - assert(word_index < s_num_adc_words); - // Where in the word the lowest bit of our ADC value is located - int first_bit_position = (s_bits_per_adc * i) % s_bits_per_word; - // How many bits of our desired ADC are located in the `word_index`th word - int bits_from_first_word = std::min(s_bits_per_adc, s_bits_per_word - first_bit_position); - uint16_t adc = adc_words[word_index] >> first_bit_position; // NOLINT - // If we didn't get the full 14 bits from this word, we need the rest from the next word - if (bits_from_first_word < s_bits_per_adc) { - assert(word_index + 1 < s_num_adc_words); - adc |= adc_words[word_index + 1] << bits_from_first_word; - } - // Mask out all but the lowest 14 bits; - return adc & 0x3FFFu; -} + /** + * @brief Get the ith ADC value in the frame + */ + uint16_t get_adc(int i) const // NOLINT + { + if (i < 0 || i >= s_num_adcs) { + throw std::out_of_range("ADC index out of range"); + } -/** - * @brief Set the ith ADC value in the frame to @p val - */ -void -set_adc(int i, uint16_t val) // NOLINT -{ - if (i < 0 || i >= s_num_adcs) - throw std::out_of_range("ADC index out of range"); - if (val >= (1 << s_bits_per_adc)) - throw std::out_of_range("ADC value out of range"); - - // The index of the first (and sometimes only) word containing the required ADC value - int word_index = s_bits_per_adc * i / s_bits_per_word; - assert(word_index < s_num_adc_words); - // Where in the word the lowest bit of our ADC value is located - int first_bit_position = (s_bits_per_adc * i) % s_bits_per_word; - // How many bits of our desired ADC are located in the `word_index`th word - int bits_in_first_word = std::min(s_bits_per_adc, s_bits_per_word - first_bit_position); - uint32_t mask = (1 << (first_bit_position)) - 1; - adc_words[word_index] = ((val << first_bit_position) & ~mask) | (adc_words[word_index] & mask); - // If we didn't put the full 14 bits in this word, we need to put the rest in the next word - if (bits_in_first_word < s_bits_per_adc) { - assert(word_index + 1 < s_num_adc_words); - mask = (1 << (s_bits_per_adc - bits_in_first_word)) - 1; - adc_words[word_index + 1] = ((val >> bits_in_first_word) & mask) | (adc_words[word_index + 1] & ~mask); + int word_index = s_bits_per_adc * i / s_bits_per_word; + assert(word_index < s_num_adc_words); + int first_bit_position = (s_bits_per_adc * i) % s_bits_per_word; + int bits_from_first_word = std::min(s_bits_per_adc, s_bits_per_word - first_bit_position); + uint16_t adc = adc_words[word_index] >> first_bit_position; // NOLINT + if (bits_from_first_word < s_bits_per_adc) { + assert(word_index + 1 < s_num_adc_words); + adc |= adc_words[word_index + 1] << bits_from_first_word; + } + return adc & 0x3FFFu; } -} - /** @brief Get the starting 64-bit timestamp of the frame + /** + * @brief Set the ith ADC value in the frame to @p val */ + void set_adc(int i, uint16_t val) // NOLINT + { + if (i < 0 || i >= s_num_adcs) { + throw std::out_of_range("ADC index out of range"); + } + if (val >= (1 << s_bits_per_adc)) { + throw std::out_of_range("ADC value out of range"); + } + + int word_index = s_bits_per_adc * i / s_bits_per_word; + assert(word_index < s_num_adc_words); + int first_bit_position = (s_bits_per_adc * i) % s_bits_per_word; + int bits_in_first_word = std::min(s_bits_per_adc, s_bits_per_word - first_bit_position); + word_t lower_mask = (static_cast(1) << first_bit_position) - 1; + adc_words[word_index] = + ((static_cast(val) << first_bit_position) & ~lower_mask) | + (adc_words[word_index] & lower_mask); + if (bits_in_first_word < s_bits_per_adc) { + assert(word_index + 1 < s_num_adc_words); + word_t upper_mask = (static_cast(1) << (s_bits_per_adc - bits_in_first_word)) - 1; + adc_words[word_index + 1] = + ((static_cast(val) >> bits_in_first_word) & upper_mask) | + (adc_words[word_index + 1] & ~upper_mask); + } + } + uint64_t get_timestamp() const // NOLINT(build/unsigned) { - return daq_header.get_timestamp() ; // NOLINT(build/unsigned) + return daq_header.get_timestamp(); } - /** @brief Set the starting 64-bit timestamp of the frame - */ void set_timestamp(const uint64_t new_timestamp) // NOLINT(build/unsigned) { daq_header.timestamp = new_timestamp; } - /** @brief Get the channel identifier of the frame - */ uint8_t get_channel() const // NOLINT(build/unsigned) { - return header.channel ; // NOLINT(build/unsigned) + return header.channel; } - /** @brief Set the channel identifier of the frame - */ void set_channel(const uint8_t new_channel) // NOLINT(build/unsigned) { header.channel = new_channel; } + const PeakDescriptorData& get_peaks_data() const + { + return header.peaks_data; + } + + PeakDescriptorData& get_peaks_data() + { + return header.peaks_data; + } }; +static_assert(sizeof(DAPHNEEthFrame::PeakDescriptorData) == + DAPHNEEthFrame::s_peak_descriptor_words * sizeof(DAPHNEEthFrame::descriptor_word_t), + "Unexpected DAPHNEEthFrame::PeakDescriptorData size"); +static_assert(sizeof(DAPHNEEthFrame::Header) == 7 * sizeof(DAPHNEEthFrame::word_t), + "Unexpected DAPHNEEthFrame::Header size"); + +inline bool +DAPHNEEthFrame::PeakDescriptorData::is_found(int idx) const +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + const descriptor_word_t* tw = as_words(); + return static_cast((tw[2 * idx] >> 31) & 0x1); +} + +inline void +DAPHNEEthFrame::PeakDescriptorData::set_found(uint8_t val, int idx) +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + if (val > 1) { + throw std::out_of_range("Found value out of range (must be 0-1)"); + } + descriptor_word_t* tw = as_words(); + tw[2 * idx] = (tw[2 * idx] & ~(descriptor_word_t(1u) << 31)) | ((val & 0x1u) << 31); +} + +inline uint32_t +DAPHNEEthFrame::PeakDescriptorData::get_adc_integral(int idx) const +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + const descriptor_word_t* tw = as_words(); + return (tw[2 * idx] >> 8) & 0x7FFFFF; +} + +inline void +DAPHNEEthFrame::PeakDescriptorData::set_adc_integral(uint32_t val, int idx) +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + if (val > 0x7FFFFF) { + throw std::out_of_range("ADC integral value out of range (must be 0-8388607)"); + } + descriptor_word_t* tw = as_words(); + tw[2 * idx] = (tw[2 * idx] & ~(descriptor_word_t(0x7FFFFFu) << 8)) | ((val & 0x7FFFFFu) << 8); +} + +inline uint8_t +DAPHNEEthFrame::PeakDescriptorData::get_num_subpeaks(int idx) const +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + const descriptor_word_t* tw = as_words(); + return static_cast(tw[2 * idx] & 0xF); +} + +inline void +DAPHNEEthFrame::PeakDescriptorData::set_num_subpeaks(uint8_t val, int idx) +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + if (val > 0xF) { + throw std::out_of_range("Num_SubPeaks value out of range (must be 0-15)"); + } + descriptor_word_t* tw = as_words(); + tw[2 * idx] = (tw[2 * idx] & ~descriptor_word_t(0xFu)) | (val & 0xFu); +} + +inline uint16_t +DAPHNEEthFrame::PeakDescriptorData::get_samples_over_baseline(int idx) const +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + const descriptor_word_t* tw = as_words(); + return static_cast((tw[2 * idx + 1] >> 23) & 0x1FF); +} + +inline void +DAPHNEEthFrame::PeakDescriptorData::set_samples_over_baseline(uint16_t val, int idx) +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + if (val > 0x1FF) { + throw std::out_of_range("Time_Over_Baseline value out of range (must be 0-511)"); + } + descriptor_word_t* tw = as_words(); + tw[2 * idx + 1] = (tw[2 * idx + 1] & ~(descriptor_word_t(0x1FFu) << 23)) | ((val & 0x1FFu) << 23); +} + +inline uint16_t +DAPHNEEthFrame::PeakDescriptorData::get_sample_max(int idx) const +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + const descriptor_word_t* tw = as_words(); + return static_cast((tw[2 * idx + 1] >> 14) & 0x1FF); +} + +inline void +DAPHNEEthFrame::PeakDescriptorData::set_sample_max(uint16_t val, int idx) +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + if (val > 0x1FF) { + throw std::out_of_range("Time_Peak value out of range (must be 0-511)"); + } + descriptor_word_t* tw = as_words(); + tw[2 * idx + 1] = (tw[2 * idx + 1] & ~(descriptor_word_t(0x1FFu) << 14)) | ((val & 0x1FFu) << 14); +} + +inline uint16_t +DAPHNEEthFrame::PeakDescriptorData::get_adc_max(int idx) const +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + const descriptor_word_t* tw = as_words(); + return static_cast(tw[2 * idx + 1] & 0x3FFF); +} + +inline void +DAPHNEEthFrame::PeakDescriptorData::set_adc_max(uint16_t val, int idx) +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Peak index out of range (must be 0-4)"); + } + if (val > 0x3FFF) { + throw std::out_of_range("ADC Max value out of range (must be 0-16383)"); + } + descriptor_word_t* tw = as_words(); + tw[2 * idx + 1] = (tw[2 * idx + 1] & ~descriptor_word_t(0x3FFFu)) | (val & 0x3FFFu); +} + +inline uint16_t +DAPHNEEthFrame::PeakDescriptorData::get_sample_start(int idx) const +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Time_Start index out of range (must be 0-4)"); + } + + const descriptor_word_t* tw = as_words(); + if (idx < 3) { + int shift = 22 - 10 * idx; + return static_cast((tw[10] >> shift) & 0x3FF); + } + + int shift = 22 - 10 * (idx - 3); + return static_cast((tw[11] >> shift) & 0x3FF); +} + +inline void +DAPHNEEthFrame::PeakDescriptorData::set_sample_start(uint16_t val, int idx) +{ + if (idx < 0 || idx > 4) { + throw std::out_of_range("Time_Start index out of range (must be 0-4)"); + } + if (val > 0x3FF) { + throw std::out_of_range("Time_Start value out of range (must be 0-1023)"); + } + + descriptor_word_t* tw = as_words(); + descriptor_word_t mask = 0x3FFu; + + if (idx < 3) { + int shift = 22 - 10 * idx; + tw[10] = (tw[10] & ~(mask << shift)) | ((val & mask) << shift); + return; + } + + int shift = 22 - 10 * (idx - 3); + tw[11] = (tw[11] & ~(mask << shift)) | ((val & mask) << shift); +} + } // namespace dunedaq::fddetdataformats #endif // FDDETDATAFORMATS_INCLUDE_FDDETDATAFORMATS_DAPHNEETHFRAME_HPP_ diff --git a/pybindsrc/daphneeth.cpp b/pybindsrc/daphneeth.cpp index 3707703..fc1c718 100644 --- a/pybindsrc/daphneeth.cpp +++ b/pybindsrc/daphneeth.cpp @@ -1,5 +1,5 @@ /** - * @file wibeth.cpp Python bindings for the DAPHNEEthFrame format + * @file daphneeth.cpp Python bindings for the DAPHNEEthFrame format * * This is part of the DUNE DAQ Software Suite, copyright 2020. * Licensing/copyright details are in the COPYING file that you should have @@ -18,84 +18,227 @@ namespace dunedaq::fddetdataformats::python { void register_daphneeth(py::module& m) { - - py::class_(m, "DAPHNEEthHeader") - // .def_property("w0", - // [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.w0;}, - // [](DAPHNEEthFrame::Header& self, uint32_t w0) {self.w0 = w0;} - // ) .def_property("w1", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.w1;}, - [](DAPHNEEthFrame::Header& self, uint32_t w1) {self.w1 = w1;} + [](DAPHNEEthFrame::Header& self) -> uint64_t { return self.get_packed_peak_word(0); }, + [](DAPHNEEthFrame::Header& self, uint64_t w1) { self.set_packed_peak_word(w1, 0); } ) .def_property("w2", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.w2;}, - [](DAPHNEEthFrame::Header& self, uint32_t w2) {self.w2 = w2;} + [](DAPHNEEthFrame::Header& self) -> uint64_t { return self.get_packed_peak_word(1); }, + [](DAPHNEEthFrame::Header& self, uint64_t w2) { self.set_packed_peak_word(w2, 1); } ) .def_property("w3", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.w3;}, - [](DAPHNEEthFrame::Header& self, uint32_t w3) {self.w3 = w3;} + [](DAPHNEEthFrame::Header& self) -> uint64_t { return self.get_packed_peak_word(2); }, + [](DAPHNEEthFrame::Header& self, uint64_t w3) { self.set_packed_peak_word(w3, 2); } ) .def_property("w4", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.w4;}, - [](DAPHNEEthFrame::Header& self, uint32_t w4) {self.w4 = w4;} + [](DAPHNEEthFrame::Header& self) -> uint64_t { return self.get_packed_peak_word(3); }, + [](DAPHNEEthFrame::Header& self, uint64_t w4) { self.set_packed_peak_word(w4, 3); } ) .def_property("w5", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.w5;}, - [](DAPHNEEthFrame::Header& self, uint32_t w5) {self.w5 = w5;} + [](DAPHNEEthFrame::Header& self) -> uint64_t { return self.get_packed_peak_word(4); }, + [](DAPHNEEthFrame::Header& self, uint64_t w5) { self.set_packed_peak_word(w5, 4); } ) .def_property("w6", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.w6;}, - [](DAPHNEEthFrame::Header& self, uint32_t w6) {self.w6 = w6;} + [](DAPHNEEthFrame::Header& self) -> uint64_t { return self.get_packed_peak_word(5); }, + [](DAPHNEEthFrame::Header& self, uint64_t w6) { self.set_packed_peak_word(w6, 5); } ) .def_property("channel", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.channel;}, - [](DAPHNEEthFrame::Header& self, uint32_t channel) {self.channel = channel;} + [](DAPHNEEthFrame::Header& self) -> uint8_t { return self.channel; }, + [](DAPHNEEthFrame::Header& self, uint8_t channel) { self.channel = channel; } ) .def_property("version", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.version;}, - [](DAPHNEEthFrame::Header& self, uint32_t version) {self.version = version;} + [](DAPHNEEthFrame::Header& self) -> uint8_t { return self.version; }, + [](DAPHNEEthFrame::Header& self, uint8_t version) { self.version = version; } ) .def_property("trigger_sample_value", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.trig_sample;}, - [](DAPHNEEthFrame::Header& self, uint32_t trig_sample) {self.trig_sample = trig_sample;} + [](DAPHNEEthFrame::Header& self) -> uint16_t { return self.trig_sample; }, + [](DAPHNEEthFrame::Header& self, uint16_t trig_sample) { self.trig_sample = trig_sample; } ) .def_property("threshold", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.threshold;}, - [](DAPHNEEthFrame::Header& self, uint32_t threshold) {self.threshold = threshold;} + [](DAPHNEEthFrame::Header& self) -> uint16_t { return self.threshold; }, + [](DAPHNEEthFrame::Header& self, uint16_t threshold) { self.threshold = threshold; } ) .def_property("baseline", - [](DAPHNEEthFrame::Header& self) -> uint32_t {return self.baseline;}, - [](DAPHNEEthFrame::Header& self, uint32_t baseline) {self.baseline = baseline;} + [](DAPHNEEthFrame::Header& self) -> uint16_t { return self.baseline; }, + [](DAPHNEEthFrame::Header& self, uint16_t baseline) { self.baseline = baseline; } + ) + ; + + py::class_(m, "DAPHNEEthFramePeakDescriptorData") + .def("is_found", &DAPHNEEthFrame::PeakDescriptorData::is_found) + .def("set_found", &DAPHNEEthFrame::PeakDescriptorData::set_found) + + .def("get_adc_integral", &DAPHNEEthFrame::PeakDescriptorData::get_adc_integral) + .def("set_adc_integral", &DAPHNEEthFrame::PeakDescriptorData::set_adc_integral) + + .def("get_num_subpeaks", &DAPHNEEthFrame::PeakDescriptorData::get_num_subpeaks) + .def("set_num_subpeaks", &DAPHNEEthFrame::PeakDescriptorData::set_num_subpeaks) + + .def("get_samples_over_baseline", &DAPHNEEthFrame::PeakDescriptorData::get_samples_over_baseline) + .def("set_samples_over_baseline", &DAPHNEEthFrame::PeakDescriptorData::set_samples_over_baseline) + + .def("get_adc_max", &DAPHNEEthFrame::PeakDescriptorData::get_adc_max) + .def("set_adc_max", &DAPHNEEthFrame::PeakDescriptorData::set_adc_max) + + .def("get_sample_max", &DAPHNEEthFrame::PeakDescriptorData::get_sample_max) + .def("set_sample_max", &DAPHNEEthFrame::PeakDescriptorData::set_sample_max) + + .def("get_sample_start", &DAPHNEEthFrame::PeakDescriptorData::get_sample_start) + .def("set_sample_start", &DAPHNEEthFrame::PeakDescriptorData::set_sample_start) + + .def_property("num_subpeaks_0", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.num_subpeaks_0; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.num_subpeaks_0 = val; } + ) + .def_property("adc_integral_0", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint32_t { return self.adc_integral_0; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint32_t val) { self.adc_integral_0 = val; } + ) + .def_property("found_0", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.found_0; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.found_0 = val; } + ) + .def_property("adc_max_0", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.adc_max_0; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.adc_max_0 = val; } + ) + .def_property("sample_max_0", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.sample_max_0; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.sample_max_0 = val; } + ) + .def_property("samples_over_baseline_0", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.samples_over_baseline_0; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.samples_over_baseline_0 = val; } + ) + + .def_property("num_subpeaks_1", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.num_subpeaks_1; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.num_subpeaks_1 = val; } + ) + .def_property("adc_integral_1", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint32_t { return self.adc_integral_1; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint32_t val) { self.adc_integral_1 = val; } + ) + .def_property("found_1", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.found_1; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.found_1 = val; } + ) + .def_property("adc_max_1", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.adc_max_1; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.adc_max_1 = val; } + ) + .def_property("sample_max_1", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.sample_max_1; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.sample_max_1 = val; } + ) + .def_property("samples_over_baseline_1", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.samples_over_baseline_1; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.samples_over_baseline_1 = val; } ) - ; + + .def_property("num_subpeaks_2", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.num_subpeaks_2; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.num_subpeaks_2 = val; } + ) + .def_property("adc_integral_2", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint32_t { return self.adc_integral_2; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint32_t val) { self.adc_integral_2 = val; } + ) + .def_property("found_2", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.found_2; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.found_2 = val; } + ) + .def_property("adc_max_2", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.adc_max_2; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.adc_max_2 = val; } + ) + .def_property("sample_max_2", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.sample_max_2; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.sample_max_2 = val; } + ) + .def_property("samples_over_baseline_2", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.samples_over_baseline_2; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.samples_over_baseline_2 = val; } + ) + + .def_property("num_subpeaks_3", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.num_subpeaks_3; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.num_subpeaks_3 = val; } + ) + .def_property("adc_integral_3", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint32_t { return self.adc_integral_3; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint32_t val) { self.adc_integral_3 = val; } + ) + .def_property("found_3", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.found_3; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.found_3 = val; } + ) + .def_property("adc_max_3", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.adc_max_3; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.adc_max_3 = val; } + ) + .def_property("sample_max_3", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.sample_max_3; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.sample_max_3 = val; } + ) + .def_property("samples_over_baseline_3", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.samples_over_baseline_3; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.samples_over_baseline_3 = val; } + ) + + .def_property("num_subpeaks_4", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.num_subpeaks_4; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.num_subpeaks_4 = val; } + ) + .def_property("adc_integral_4", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint32_t { return self.adc_integral_4; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint32_t val) { self.adc_integral_4 = val; } + ) + .def_property("found_4", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint8_t { return self.found_4; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint8_t val) { self.found_4 = val; } + ) + .def_property("adc_max_4", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.adc_max_4; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.adc_max_4 = val; } + ) + .def_property("sample_max_4", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.sample_max_4; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.sample_max_4 = val; } + ) + .def_property("samples_over_baseline_4", + [](DAPHNEEthFrame::PeakDescriptorData& self) -> uint16_t { return self.samples_over_baseline_4; }, + [](DAPHNEEthFrame::PeakDescriptorData& self, uint16_t val) { self.samples_over_baseline_4 = val; } + ) + ; py::class_(m, "DAPHNEEthFrame", py::buffer_protocol()) .def(py::init()) .def(py::init([](py::capsule capsule) { auto wfp = *static_cast(capsule.get_pointer()); return wfp; - } )) - .def(py::init([](py::bytes bytes){ + })) + .def(py::init([](py::bytes bytes) { py::buffer_info info(py::buffer(bytes).request()); auto wfp = *static_cast(info.ptr); return wfp; })) - .def("get_daqheader", [](DAPHNEEthFrame& self) -> const detdataformats::DAQEthHeader& {return self.daq_header;}, py::return_value_policy::reference_internal) - .def("get_daphneheader", [](DAPHNEEthFrame& self) -> const DAPHNEEthFrame::Header& {return self.header;}, py::return_value_policy::reference_internal) - .def("get_header", [](DAPHNEEthFrame& self) -> const DAPHNEEthFrame::Header& {return self.header;}, py::return_value_policy::reference_internal) + .def("get_daqheader", [](DAPHNEEthFrame& self) -> const detdataformats::DAQEthHeader& { return self.daq_header; }, py::return_value_policy::reference_internal) + .def("get_daphneheader", [](DAPHNEEthFrame& self) -> const DAPHNEEthFrame::Header& { return self.header; }, py::return_value_policy::reference_internal) + .def("get_header", [](DAPHNEEthFrame& self) -> const DAPHNEEthFrame::Header& { return self.header; }, py::return_value_policy::reference_internal) + .def("get_peaks_data", [](DAPHNEEthFrame& self) -> const DAPHNEEthFrame::PeakDescriptorData& { return self.get_peaks_data(); }, py::return_value_policy::reference_internal) .def("get_adc", &DAPHNEEthFrame::get_adc) .def("set_adc", &DAPHNEEthFrame::set_adc) .def("get_timestamp", &DAPHNEEthFrame::get_timestamp) .def("set_timestamp", &DAPHNEEthFrame::set_timestamp) .def("get_channel", &DAPHNEEthFrame::get_channel) .def("set_channel", &DAPHNEEthFrame::set_channel) - .def_static("sizeof", [](){ return sizeof(DAPHNEEthFrame); }) + .def_static("sizeof", []() { return sizeof(DAPHNEEthFrame); }) .def("get_bytes", [](DAPHNEEthFrame* fr) -> py::bytes { return py::bytes(reinterpret_cast(fr), sizeof(DAPHNEEthFrame)); - }) + }) ; } diff --git a/unittest/DAPHNEEthFrame_test.cxx b/unittest/DAPHNEEthFrame_test.cxx new file mode 100644 index 0000000..339c4bc --- /dev/null +++ b/unittest/DAPHNEEthFrame_test.cxx @@ -0,0 +1,96 @@ +/** + * @file DAPHNEEthFrame_test.cxx - Unit tests for DAPHNEEthFrame + */ + +#include "fddetdataformats/DAPHNEEthFrame.hpp" + +#define BOOST_TEST_MODULE DAPHNEEthFrame_test + +#include "boost/test/unit_test.hpp" + +#include +#include +#include + +BOOST_AUTO_TEST_SUITE(DAPHNEEthFrame_test) + +BOOST_AUTO_TEST_CASE(DAPHNEEthFrame_AllFieldsTest) +{ + constexpr int n_adcs = dunedaq::fddetdataformats::DAPHNEEthFrame::s_num_adcs; + constexpr int n_peaks = dunedaq::fddetdataformats::DAPHNEEthFrame::PeakDescriptorData::max_peaks; + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution adc_dist(1, (1 << 14) - 1); + std::uniform_int_distribution u10bit(0, 0x3FF); + std::uniform_int_distribution u9bit(0, 0x1FF); + std::uniform_int_distribution u4bit(0, 0xF); + std::uniform_int_distribution u23bit(0, 0x7FFFFF); + std::uniform_int_distribution u14bit(0, 0x3FFF); + std::uniform_int_distribution u1bit(0, 1); + + dunedaq::fddetdataformats::DAPHNEEthFrame frame{}; + + BOOST_CHECK_EQUAL(sizeof(dunedaq::fddetdataformats::DAPHNEEthFrame::PeakDescriptorData), 12 * sizeof(uint32_t)); + BOOST_CHECK_EQUAL(sizeof(dunedaq::fddetdataformats::DAPHNEEthFrame::Header), 7 * sizeof(uint64_t)); + + std::vector adcs(n_adcs); + std::generate(adcs.begin(), adcs.end(), [&]() { return adc_dist(gen); }); + + for (int i = 0; i < n_adcs; ++i) { + frame.set_adc(i, adcs[i]); + } + + for (int i = 0; i < n_adcs; ++i) { + BOOST_CHECK_EQUAL(frame.get_adc(i), adcs[i]); + } + + frame.set_channel(23); + frame.header.trig_sample = 0x2AAA; + frame.header.threshold = 0x1555; + frame.header.baseline = 0x0123; + frame.header.version = dunedaq::fddetdataformats::DAPHNEEthFrame::version; + frame.set_timestamp(0x123456789ABCDEF0ULL); + + BOOST_CHECK_EQUAL(frame.get_channel(), 23); + BOOST_CHECK_EQUAL(frame.header.trig_sample, 0x2AAA); + BOOST_CHECK_EQUAL(frame.header.threshold, 0x1555); + BOOST_CHECK_EQUAL(frame.header.get_baseline(), 0x0123); + BOOST_CHECK_EQUAL(frame.header.version, dunedaq::fddetdataformats::DAPHNEEthFrame::version); + BOOST_CHECK_EQUAL(frame.get_timestamp(), 0x123456789ABCDEF0ULL); + + for (int peak = 0; peak < n_peaks; ++peak) { + uint8_t num_subpeaks = u4bit(gen); + uint8_t found = u1bit(gen); + uint32_t adc_integral = u23bit(gen); + uint16_t adc_max = u14bit(gen); + uint16_t sample_peak = u9bit(gen); + uint16_t tob = u9bit(gen); + uint16_t sample_start = u10bit(gen); + + frame.get_peaks_data().set_num_subpeaks(num_subpeaks, peak); + frame.get_peaks_data().set_found(found, peak); + frame.get_peaks_data().set_adc_integral(adc_integral, peak); + frame.get_peaks_data().set_adc_max(adc_max, peak); + frame.get_peaks_data().set_sample_max(sample_peak, peak); + frame.get_peaks_data().set_samples_over_baseline(tob, peak); + frame.get_peaks_data().set_sample_start(sample_start, peak); + + BOOST_CHECK_EQUAL(frame.get_peaks_data().get_num_subpeaks(peak), num_subpeaks); + BOOST_CHECK_EQUAL(frame.get_peaks_data().is_found(peak), found); + BOOST_CHECK_EQUAL(frame.get_peaks_data().get_adc_integral(peak), adc_integral); + BOOST_CHECK_EQUAL(frame.get_peaks_data().get_adc_max(peak), adc_max); + BOOST_CHECK_EQUAL(frame.get_peaks_data().get_sample_max(peak), sample_peak); + BOOST_CHECK_EQUAL(frame.get_peaks_data().get_samples_over_baseline(peak), tob); + BOOST_CHECK_EQUAL(frame.get_peaks_data().get_sample_start(peak), sample_start); + } + + for (int packed_word = 0; packed_word < dunedaq::fddetdataformats::DAPHNEEthFrame::s_packed_peak_descriptor_words; ++packed_word) { + auto value = frame.header.get_packed_peak_word(packed_word); + dunedaq::fddetdataformats::DAPHNEEthFrame::word_t roundtrip = 0; + roundtrip = value; + BOOST_CHECK_EQUAL(roundtrip, value); + } +} + +BOOST_AUTO_TEST_SUITE_END()