From 0497349124c4441e1eace700b09f9ca52d73d1a9 Mon Sep 17 00:00:00 2001
From: Alexandru Bercuci <abercuci@niham.nipne.ro>
Date: Wed, 15 Jan 2025 15:30:15 +0200
Subject: [PATCH] 2D online unpacker. Updated versions for the algo and the
 FASP message. Template implementation

---
 algo/detectors/trd2d/ReadoutConfig.cxx |  14 +-
 algo/detectors/trd2d/ReadoutConfig.h   |   8 +-
 algo/detectors/trd2d/Unpack.cxx        |  31 ++-
 algo/detectors/trd2d/UnpackMS.cxx      | 283 +++++++++++++++++++++++--
 algo/detectors/trd2d/UnpackMS.h        | 189 +++++++++++++----
 5 files changed, 452 insertions(+), 73 deletions(-)

diff --git a/algo/detectors/trd2d/ReadoutConfig.cxx b/algo/detectors/trd2d/ReadoutConfig.cxx
index 3aaa79b1c2..3258171b02 100644
--- a/algo/detectors/trd2d/ReadoutConfig.cxx
+++ b/algo/detectors/trd2d/ReadoutConfig.cxx
@@ -1,6 +1,6 @@
-/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2023-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Volker Friese, Dominik Smith [committer] */
+   Authors: Volker Friese, Dominik Smith [committer], Alex Bercuci */
 
 #include "ReadoutConfig.h"
 
@@ -73,10 +73,10 @@ namespace cbm::algo::trd2d
     // Invert to obtain component map (equipId) -> (module iq, crob id)
     for (auto& entry : map) {
       uint16_t mod_id = entry.first;
-      for (uint8_t crob_id = 0; crob_id < NCROBMOD; crob_id++) {
-        uint16_t eq_id = entry.second[crob_id];
+      for (uint8_t rob_id = 0; rob_id < NCROBMOD; rob_id++) {
+        uint16_t eq_id = entry.second[rob_id];
         if (!eq_id) continue;
-        fReadoutMap[eq_id] = {mod_id, crob_id};
+        fReadoutMap[eq_id] = {mod_id, rob_id};
       }
     }
   }
@@ -146,8 +146,8 @@ namespace cbm::algo::trd2d
       uint16_t equipmentId = comp.first;
       auto value           = comp.second;
       uint16_t moduleId    = value.moduleId;
-      uint16_t crobId      = value.crobId;
-      ss << "Equipment " << equipmentId << " Module " << moduleId << " Crob " << crobId << "\n";
+      uint16_t robId       = value.robId;
+      ss << "Equipment " << equipmentId << " Module " << moduleId << " Rob " << robId << "\n";
     }
     ss << "\n";
 
diff --git a/algo/detectors/trd2d/ReadoutConfig.h b/algo/detectors/trd2d/ReadoutConfig.h
index 9d43ea1e5f..4631e1c0a5 100644
--- a/algo/detectors/trd2d/ReadoutConfig.h
+++ b/algo/detectors/trd2d/ReadoutConfig.h
@@ -1,6 +1,6 @@
-/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2023-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Volker Friese, Dominik Smith [committer] */
+   Authors: Volker Friese, Dominik Smith [committer], Alex Bercuci */
 
 #pragma once
 
@@ -34,11 +34,11 @@ namespace cbm::algo::trd2d
    public:
     struct CompMapping {
       uint16_t moduleId;
-      uint8_t crobId;
+      uint8_t robId;
 
       CBM_YAML_FORMAT(YAML::Flow);
       CBM_YAML_PROPERTIES(yaml::Property(&CompMapping::moduleId, "moduleId", "Module ID"),
-                     yaml::Property(&CompMapping::crobId, "crobId", "CROB ID"));
+                     yaml::Property(&CompMapping::robId, "crobId", "CROB ID"));
     };
 
     struct ChanMapping {
diff --git a/algo/detectors/trd2d/Unpack.cxx b/algo/detectors/trd2d/Unpack.cxx
index ef628a177d..c8a1ee8034 100644
--- a/algo/detectors/trd2d/Unpack.cxx
+++ b/algo/detectors/trd2d/Unpack.cxx
@@ -1,6 +1,6 @@
-/* Copyright (C) 2024 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+/* Copyright (C) 2024-2025 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Felix Weiglhofer [committer], Dominik Smith */
+   Authors: Felix Weiglhofer [committer], Dominik Smith, Alex Bercuci */
 
 #include "Unpack.h"
 
@@ -11,7 +11,14 @@ using fles::Subsystem;
 
 Unpack::Unpack(const ReadoutConfig& readout) : fReadout(readout)
 {
-  constexpr u8 SystemVersion = 0x02;
+  /** Register the algorithm versions available for TRD2D. For the moment (25.01.14), there is 
+ * no distinction between ALGO and MESSAGE version. The following mapping is 
+ * assumed:
+ * eMessageVersion::kMessLegacy - refers to the version WITH digi buffering
+ * eMessageVersion::kMess24     - refers to the version WITHOUT digi buffering
+ */
+  constexpr std::array<u8, int(eMessageVersion::kMessNoDef)> AlgoVersion = {(u8) eMessageVersion::kMessLegacy,
+                                                                            (u8) eMessageVersion::kMess24};
 
   // Create one algorithm per component for TRD and configure it with parameters
   auto equipIdsTrd2d = fReadout.GetEquipmentIds();
@@ -35,11 +42,23 @@ Unpack::Unpack(const ReadoutConfig& readout) : fReadout(readout)
       auto comppars         = fReadout.CompMap(equip);
       par.fSystemTimeOffset = fReadout.GetSystemTimeOffset();
       par.fModId            = comppars.moduleId;
-      par.fCrobId           = comppars.crobId;
+      par.fRobId            = comppars.robId;
       par.fAsicParams.push_back(asicPar);
     }
-    auto algo                      = std::make_unique<UnpackMS>(par);
-    fAlgos[{equip, SystemVersion}] = std::move(algo);
+    // build all algorithms defined for data unpacking ! Why ?? (AB 25.01.15)
+    std::unique_ptr<UnpackMSBase<CbmTrdDigi, UnpackMonitorData, UnpackAuxData>> algo;
+    for (auto ver : AlgoVersion) {
+      switch (ver) {
+        case (int) eMessageVersion::kMessLegacy:
+          algo = std::make_unique<UnpackMS<(u8) eMessageVersion::kMessLegacy>>(par);
+          break;
+        case (int) eMessageVersion::kMess24:
+          algo = std::make_unique<UnpackMS<(u8) eMessageVersion::kMess24>>(par);
+          break;
+      }
+      // register algorithm
+      fAlgos[{equip, ver}] = std::move(algo);
+    }
 
     L_(debug) << "--- Configured equipment " << equip << " with " << numAsics << " asics";
   }
diff --git a/algo/detectors/trd2d/UnpackMS.cxx b/algo/detectors/trd2d/UnpackMS.cxx
index 714f4c2e81..9bdf5b40d3 100644
--- a/algo/detectors/trd2d/UnpackMS.cxx
+++ b/algo/detectors/trd2d/UnpackMS.cxx
@@ -8,6 +8,7 @@
 
 #include <algorithm>
 #include <cassert>
+#include <sstream>
 #include <vector>
 
 using std::unique_ptr;
@@ -15,23 +16,135 @@ using std::unique_ptr;
 namespace cbm::algo::trd2d
 {
   // ----   Fasp message constructor  ----------------------------------------
-  FaspMessage::FaspMessage(uint8_t c, uint8_t typ, uint8_t t, uint16_t d, uint8_t rob, uint8_t asic)
+  FaspMessage::FaspMessage(uint8_t c, uint8_t typ, uint8_t t, uint16_t d, uint8_t r, uint8_t asic, uint8_t e)
     : ch(c)
-    , type(typ)
+    , type(eMessageType::kNone)
     , tlab(t)
     , data(d)
-    , crob(rob)
+    , rob(r)
+    , elink(e)
     , fasp(asic)
   {
+    if (typ == uint8_t(eMessageType::kData))
+      type = eMessageType::kData;
+    else if (typ == uint8_t(eMessageType::kEpoch))
+      type = eMessageType::kEpoch;
   }
 
-  UnpackMS::UnpackMS(const UnpackPar& pars) : fParams(pars) {}
-  UnpackMS::~UnpackMS() = default;
+  std::string FaspMessage::print() const
+  {
+    std::stringstream ss;
+    switch (type) {
+      case eMessageType::kData:
+        ss << "    DATA : rob=" << rob << (elink ? 'u' : 'd') << " fasp_id=" << fasp << " [" << getFaspIdMod()
+           << "] ch_id=" << ch << " tclk=" << tlab << " data=" << data << std::endl;
+        break;
+      case eMessageType::kEpoch:
+        ss << "    EPOCH: eq_id=" << rob << (elink ? 'u' : 'd') << " ch_id=" << ch << " epoch=" << epoch << std::endl;
+        break;
+      default: ss << "    MTYPE: unknown" << std::endl; break;
+    }
+
+    return ss.str();
+  }
+
+  // ----   Data type descriptor   ---------------------------------------------
+  template<std::uint8_t mess_ver>
+  eMessageType FaspMessage::getType(uint32_t)
+  {
+    return eMessageType::kNone;
+  }
+
+  template<>
+  eMessageType FaspMessage::getType<uint8_t(eMessageVersion::kMess24)>(uint32_t w)
+  {
+    /** Search the data type descriptor in a FASP word. Starting with message version kMess24*/
+
+    if ((w >> 31) & 0x1) return eMessageType::kEpoch;
+    return eMessageType::kData;
+  }
+
+  // ----   Unpacking a DATA WORD   ---------------------------------------------
+  template<std::uint8_t mess_ver>
+  void FaspMessage::readDW(uint32_t)
+  {
+    return;
+  }
+
+  template<>
+  void FaspMessage::readDW<uint8_t(eMessageVersion::kMess24)>(uint32_t w)
+  {
+    /** Data Word unpacking starting with message version kMess24*/
+
+    uint8_t shift(0);
+    uint16_t adc_data = (w >> shift) & 0x3fff;
+    // TODO This data format version delivers the ADC value as bit_sgn + 13 significant bits
+    // TODO The CbmTrdDigi supports digi data with only 12bits unsigned. The first tests will
+    // TODO convert the measurement to the old format leaving the implementation of the new storage to // TODO later time.  (AB 14.06.2024)
+    uint16_t sign = adc_data >> 13;  // sign
+    int value_i;
+    if (!sign)
+      value_i = adc_data;
+    else
+      value_i = (-1) * ((adc_data ^ 0xffff) & 0x1fff);
+    // convert to 12bit unsigned
+    data = (value_i + 0x1fff) >> 2;
+    shift += uint8_t(eMessageLength::kMessData);
+    tlab = (w >> shift) & 0x7f;
+    shift += uint8_t(eMessageLength::kMessTlab);
+    ch = (w >> shift) & 0xf;
+    shift += uint8_t(eMessageLength::kMessCh);
+    fasp = ((w >> shift) & 0x3f);
+    shift += uint8_t(eMessageLength::kMessFasp);
+    type = eMessageType((w >> shift) & 0x1);
+    shift += uint8_t(eMessageLength::kMessType);
+
+    // if (VERBOSE >= 2) {
+    //   printf("v06.24Mess_readDW[%x] signed charge = %+d\n", w, value_i);
+    //   print();
+    // }
+    return;
+  }
+
+  // ----   Unpacking an EPOCH WORD   ---------------------------------------------
+  template<std::uint8_t mess_ver>
+  void FaspMessage::readEW(uint32_t)
+  {
+    return;
+  }
+
+  template<>
+  void FaspMessage::readEW<uint8_t(eMessageVersion::kMess24)>(uint32_t w)
+  {
+    /** Epoch Word unpacking starting with message version kMess24*/
+
+    uint8_t shift(0);
+    epoch = (w >> shift) & 0x1fffff;
+    shift += uint8_t(eMessageLength::kMessEpoch);
+    ch = (w >> shift) & 0xf;
+    shift += uint8_t(eMessageLength::kMessCh);
+    fasp = (w >> shift) & 0x3f;
+    shift += uint8_t(eMessageLength::kMessFasp);
+    type = eMessageType((w >> shift) & 0x1);
+    shift += uint8_t(eMessageLength::kMessType);
+
+    // if (VERBOSE >= 2) {
+    //   printf("v06.24Mess_readEW[%x]\n", w);
+    //   print();
+    // }
+    return;
+  }
 
   // ----   Algorithm execution   ---------------------------------------------
-  UnpackMS::Result_t UnpackMS::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
-                                          const uint64_t tTimeslice) const
+  template<>
+  typename UnpackMS<uint8_t(eMessageVersion::kMessLegacy)>::Result_t
+  UnpackMS<uint8_t(eMessageVersion::kMessLegacy)>::operator()(const uint8_t* msContent,
+                                                              const fles::MicrosliceDescriptor& msDescr,
+                                                              const uint64_t tTimeslice) const
   {
+    /** Implementation of TRD2D unpacking for the 2021 - 2022 mCBM data taking
+     * The algorithm implements digi buffering due to a "feature" on the ADC read-out
+     */
     // --- Output data
     Result_t result = {};
 
@@ -44,12 +157,12 @@ namespace cbm::algo::trd2d
     uint64_t time = uint64_t((msDescr.idx - tTimeslice - fParams.fSystemTimeOffset) / 12.5);
 
     // Get parameters for current eq id.
-    const uint8_t crob_id = fParams.fCrobId;
+    const uint8_t crob_id = fParams.fRobId;
 
     // Get the number of complete words in the input MS buffer.
     const uint32_t nwords = msDescr.size / 4;
 
-    // We have 32 bit spadic frames in this readout version
+    // We have 32 bit FASP frames in this readout version
     const uint32_t* wd = reinterpret_cast<const uint32_t*>(msContent);
 
     unsigned char lFaspOld(0xff);
@@ -90,16 +203,16 @@ namespace cbm::algo::trd2d
         ctx.fMonitor.fNumSelfTriggeredData++;
         data &= 0x1fff;
       }
-      vMess.emplace_back(ch_id, kData, slice, data >> 1, crob_id, lFaspOld);
+      vMess.emplace_back(ch_id, (uint8_t) eMessageType::kData, slice, data >> 1, crob_id, lFaspOld);
     }
     std::get<0>(result) = FinalizeComponent(ctx);  //TO DO: Original (non-algo) version calls this after MS loop!!
     std::get<1>(result) = ctx.fMonitor;
 
     return result;
   }
-
   //_________________________________________________________________________________
-  bool UnpackMS::pushDigis(std::vector<FaspMessage> messes, const uint64_t time, MsContext& ctx) const
+  template<uint8_t sys_ver>
+  bool UnpackMS<sys_ver>::pushDigis(std::vector<FaspMessage> messes, const uint64_t time, MsContext& ctx) const
   {
     const uint16_t mod_id        = fParams.fModId;
     const UnpackAsicPar& asicPar = fParams.fAsicParams[messes[0].fasp];
@@ -167,7 +280,8 @@ namespace cbm::algo::trd2d
     return true;
   }
 
-  std::vector<CbmTrdDigi> UnpackMS::FinalizeComponent(MsContext& ctx) const
+  template<uint8_t sys_ver>
+  std::vector<CbmTrdDigi> UnpackMS<sys_ver>::FinalizeComponent(MsContext& ctx) const
   {
     std::vector<CbmTrdDigi> outputDigis;
 
@@ -196,4 +310,147 @@ namespace cbm::algo::trd2d
     }
     return outputDigis;
   }
+
+  // ------------- Specialization kMess24 --------------------------------
+  typename UnpackMS<uint8_t(eMessageVersion::kMess24)>::Result_t
+  UnpackMS<uint8_t(eMessageVersion::kMess24)>::operator()(const uint8_t* msContent,
+                                                          const fles::MicrosliceDescriptor& msDescr,
+                                                          const uint64_t tTimeslice) const
+  {
+    /** Implementation of TRD2D unpacking for the 2024 - PRESENT mCBM data taking
+     * The algorithm implements the new message format starting with version kMess24
+     */
+
+    constexpr uint8_t m24 =
+      uint8_t(eMessageVersion::kMess24);  // message versions compatible with the current algo specialization
+    Result_t result = {};
+    MsContext ctx   = {};
+
+    // define time wrt start of time slice in TRD/FASP clks [80 MHz]. Contains:
+    //  - relative offset of the MS wrt the TS
+    //  - FASP epoch offset for current CROB
+    //  - TRD2D system offset wrt to experiment time
+    uint64_t time = uint64_t((msDescr.idx - tTimeslice - fParams.fSystemTimeOffset) * fAsicClockFreq);
+
+    // Get the number of complete words in the input MS buffer.
+    const uint32_t nwords = msDescr.size / 4;
+
+    // We have 32 bit FASP frames in this readout version
+    const uint32_t* wd = reinterpret_cast<const uint32_t*>(msContent);
+
+    unsigned char lFaspOld(0xff);
+    std::vector<FaspMessage> vMess;
+    for (uint32_t j = 0; j < nwords; j++, wd++) {
+      uint32_t w = *wd;
+      // Select the appropriate conversion type of the word according to
+      // the current message version and type
+      switch (FaspMessage::getType<m24>(w)) {
+        case eMessageType::kData: ctx.fMess.readDW<m24>(w); break;
+        case eMessageType::kEpoch: ctx.fMess.readEW<m24>(w); break;
+        default: break;  // no way to reach this line
+      }
+      ctx.fMess.rob = fParams.fRobId;
+
+      // PROCESS EPOCH MESSAGES
+      if (ctx.fMess.type == eMessageType::kEpoch) {
+        if (ctx.fMess.ch == 0) {  // check word integrity
+          // clear buffer
+          if (vMess.size()) {
+            pushDigis(vMess, time, ctx);
+          }
+          vMess.clear();
+
+          lFaspOld = 0xff;
+          time += FASP_EPOCH_LENGTH;
+        }
+        else {
+          L_(error) << "FASP message[Epoch] with wrong signature.";
+          ctx.fMonitor.fNumErrEndBitSet++;
+        }
+        continue;
+      }
+
+      // PROCESS DATA MESSAGES
+      // clear buffer when switching to other FASP
+      if (ctx.fMess.fasp != lFaspOld) {
+        if (vMess.size()) pushDigis(vMess, time, ctx);
+        vMess.clear();
+        lFaspOld = ctx.fMess.fasp;
+      }
+      if (ctx.fMess.data & 0x1) {  // kept for backward compatibility TODO
+        ctx.fMonitor.fNumErrEndBitSet++;
+        continue;
+      }
+      if (ctx.fMess.data & 0x2000) {  // kept for backward compatibility TODO
+        ctx.fMonitor.fNumSelfTriggeredData++;
+        ctx.fMess.data &= 0x1fff;
+      }
+      vMess.emplace_back(ctx.fMess);
+    }
+
+    // combine all digis from this ROB
+    std::vector<CbmTrdDigi> outputDigis;
+    for (uint16_t ipad(0); ipad < NFASPMOD * NFASPCH; ipad++) {
+      if (!ctx.fRobDigi[ipad].size()) continue;
+      for (auto id : ctx.fRobDigi[ipad])
+        outputDigis.emplace_back(std::move(id));
+    }
+    std::get<0>(result) = outputDigis;
+    std::get<1>(result) = ctx.fMonitor;
+
+    return result;
+  }
+  bool UnpackMS<uint8_t(eMessageVersion::kMess24)>::pushDigis(std::vector<FaspMessage> messes, const uint64_t time,
+                                                              MsContext& ctx) const
+  {
+    const uint16_t mod_id        = fParams.fModId;
+    const UnpackAsicPar& asicPar = fParams.fAsicParams[messes[0].fasp];
+    const uint64_t tdaqOffset    = asicPar.fChanParams[messes[0].ch].fDaqOffset;
+
+    for (auto imess : messes) {
+      const int32_t pad                   = std::abs(asicPar.fChanParams[imess.ch].fPadAddress);
+      const bool hasPairingR              = bool(asicPar.fChanParams[imess.ch].fPadAddress > 0);
+      const uint64_t lTime                = time + tdaqOffset + imess.tlab;
+      const uint16_t lchR                 = hasPairingR ? imess.data : 0;
+      const uint16_t lchT                 = hasPairingR ? 0 : imess.data;
+      std::vector<CbmTrdDigi>& digiBuffer = ctx.fRobDigi[pad];
+
+      // init pad position in array and build digi for message
+      if (digiBuffer.size() == 0) {
+        digiBuffer.emplace_back(pad, lchT, lchR, lTime);
+        digiBuffer.back().SetAddressModule(mod_id);
+        continue;
+      }
+
+      // check if last digi has both R/T message components.
+      // Update if not and is within time window
+      auto id = digiBuffer.back();  // Should always be valid here.
+                                    // No need to extra check
+      double r, t;
+      int32_t dt;
+      const int32_t dtime = id.GetTimeDAQ() - lTime;
+      bool use(false);
+      if (abs(dtime) < 5) {  // test message part of (last) digi
+        r = id.GetCharge(t, dt);
+        if (lchR && r < 0.1) {  // set R charge on an empty slot
+          id.SetCharge(t, lchR, -dtime);
+          use = true;
+        }
+        else if (lchT && t < 0.1) {  // set T charge on an empty slot
+          id.SetCharge(lchT, r, +dtime);
+          id.SetTimeDAQ(uint64_t(id.GetTimeDAQ() - dtime));
+          use = true;
+        }
+      }
+
+      // build digi for message when update failed
+      if (!use) {
+        digiBuffer.emplace_back(pad, lchT, lchR, lTime);
+        digiBuffer.back().SetAddressModule(mod_id);
+      }
+    }
+    messes.clear();
+
+    return true;
+  }
 }  // namespace cbm::algo::trd2d
diff --git a/algo/detectors/trd2d/UnpackMS.h b/algo/detectors/trd2d/UnpackMS.h
index 18a498eecc..6491945e0e 100644
--- a/algo/detectors/trd2d/UnpackMS.h
+++ b/algo/detectors/trd2d/UnpackMS.h
@@ -4,7 +4,6 @@
 #pragma once
 
 #include "CbmTrdDigi.h"
-#include "CbmTrdRawMessageSpadic.h"
 #include "MicrosliceDescriptor.hpp"
 #include "UnpackMSBase.h"
 
@@ -21,30 +20,102 @@
 
 namespace cbm::algo::trd2d
 {
+  enum class eMessageLength : int
+  {
+    kMessCh    = 4,
+    kMessType  = 1,
+    kMessTlab  = 7,
+    kMessData  = 14,
+    kMessFasp  = 6,
+    kMessEpoch = 21
+  };
+
+  enum class eMessageVersion : uint8_t
+  {
+    kMessLegacy = 2,  /// unpacker version for 2-board FASPRO+GETS HW
+    kMess24     = 3,  /// unpacker version for 1-board FASPRO HW first used 18.06.24 (mCBM)
+    kMessNoDef        /// default unpacker version
+  };
 
-  enum FaspMessageType
+  enum class eMessageType : int
   {
-    kEpoch = 0,
-    kData
+    kData  = 0,
+    kEpoch = 1,
+    kNone
   };
 
   /** @brief Data structure for unpacking the FASP word */
+  // Constants
+  /** @brief Bytes per FASP frame stored in the microslices (32 bits words)
+  * - DATA WORD - for legacy version
+  * ffff.ffdd dddd.dddd dddd.tttt ttta.cccc
+  * f - FASP id
+  * d - ADC signal
+  * t - time label inside epoch
+  * a - word type (1)
+  * c - channel id
+  * - EPOCH WORD -
+  * ffff.fftt tttt.tttt tttt.tttt ttta.cccc
+  * f - FASP id
+  * t - epoch index
+  * a - word type (0)
+  * c - channel id
+  * =====================================================
+  * - DATA WORD - for 06.2024 version
+  * afff.fffc ccct.tttt ttdd.dddd dddd.dddd
+  * f - FASP id
+  * d - ADC signal
+  * t - time label inside epoch
+  * a - word type (1)
+  * c - channel id
+  * - EPOCH WORD -
+  * afff.fffc ccct.tttt tttt tttt tttt.tttt
+  * a - word type (0)
+  * f - FASP id
+  * t - epoch index
+  * c - channel id
+  */
   struct FaspMessage {
-    FaspMessage(uint8_t c, uint8_t typ, uint8_t t, uint16_t d, uint8_t rob, uint8_t asic);
-    uint8_t ch     = 0;  ///< ch id in the FASP
-    uint8_t type   = 0;  ///< message type 0 = epoch, 1 = data (not used for the moment)
-    uint8_t tlab   = 0;  ///< time of the digi inside the epoch
-    uint16_t data  = 0;  ///< ADC value
-    uint32_t epoch = 0;  ///< epoch id (not used for the moment)
-    uint32_t mod   = 0;  ///< full module address according to CbmTrdAddress
-    uint8_t crob   = 0;  ///< CROB id in the module
-    uint8_t fasp   = 0;  ///< FASP id in the module
+    FaspMessage()                   = default;
+    FaspMessage(const FaspMessage&) = default;
+    FaspMessage(uint8_t c, uint8_t typ, uint8_t t, uint16_t d, uint8_t rob, uint8_t asic, uint8_t e = 0);
+    int getFaspIdMod() const { return fasp + rob * NFASPCROB; }
+
+    /** \brief Implementation of message type descriptor according to message version
+     * \param w the message word
+     */
+    template<std::uint8_t mess_ver>
+    static eMessageType getType(uint32_t w);
+
+    std::string print() const;
+
+    /** \brief Read DATA WORD and store the content locally
+     * \param w the message word
+     */
+    template<std::uint8_t mess_ver>
+    void readDW(uint32_t w);
+
+    /** \brief Read EPOCH WORD and store the content locally
+     * \param w the message word
+     */
+    template<std::uint8_t mess_ver>
+    void readEW(uint32_t w);
+
+    uint8_t ch        = 0;                    ///< ch id in the FASP
+    eMessageType type = eMessageType::kNone;  ///< message type 0 = data, 1 = epoch (not used for the moment)
+    uint8_t tlab      = 0;                    ///< time of the digi inside the epoch
+    uint16_t data     = 0;                    ///< ADC value
+    uint32_t epoch    = 0;                    ///< epoch id (not used for the moment)
+    uint32_t mod      = 0;                    ///< full module address according to CbmTrdAddress
+    uint8_t rob       = 0;                    ///< ROB id in the module
+    uint8_t elink     = 0;                    ///< optical link for read-out unit (up or down, starting with kMess24)
+    uint8_t fasp      = 0;                    ///< FASP id in the module
   };
 
   /** @struct UnpackChannelPar
    ** @author Dominik Smith <d.smith@gsi.de>
    ** @since 31 January 2023
-   ** @brief TRD Unpacking parameters for one Asic channel
+   ** @brief TRD2D Unpacking parameters for one Asic channel
    **/
   struct UnpackChannelPar {
     int32_t fPadAddress;      ///< Pad address for channel
@@ -55,7 +126,7 @@ namespace cbm::algo::trd2d
   /** @struct UnpackAsicPar
    ** @author Dominik Smith <d.smith@gsi.de>
    ** @since 31 January 2023
-   ** @brief TRD Unpacking parameters for one Asic
+   ** @brief TRD2D Unpacking parameters for one Asic
    **/
   struct UnpackAsicPar {
     std::vector<UnpackChannelPar> fChanParams;  ///< Parameters for different channels
@@ -64,12 +135,12 @@ namespace cbm::algo::trd2d
   /** @struct UnpackPar
    ** @author Dominik Smith <d.smith@gsi.de>
    ** @since 31 January 2023
-   ** @brief Parameters required for the TRD unpacking (specific to one component)
+   ** @brief Parameters required for the TRD2D unpacking (specific to one component)
    **/
   struct UnpackPar {
     int32_t fSystemTimeOffset              = 0;   ///< Time calibration parameter
     uint16_t fModId                        = 0;   ///< Module ID of component
-    uint8_t fCrobId                        = 0;   ///< CROB ID of component
+    uint8_t fRobId                         = 0;   ///< ROB ID of component
     std::vector<UnpackAsicPar> fAsicParams = {};  ///< Parameters for each ASIC
   };
 
@@ -77,7 +148,7 @@ namespace cbm::algo::trd2d
   /** @struct UnpackMoni
    ** @author Dominik Smith <d.smith@gsi.de>
    ** @since 31 January 2023
-   ** @brief Monitoring data for TRD unpacking
+   ** @brief Monitoring data for TRD2D unpacking
    **/
   struct UnpackMonitorData {
     uint32_t fNumSelfTriggeredData = 0;  ///< word fulfills data & 0x2000
@@ -101,27 +172,26 @@ namespace cbm::algo::trd2d
   /** @struct UnpackAux
    ** @author Dominik Smith <d.smith@gsi.de>
    ** @since 24 May 2024
-   ** @brief Auxiliary data for BMON unpacking
+   ** @brief Auxiliary data for unpacking
    **/
   struct UnpackAuxData {
     ///// TO BE FILLED
   };
 
   /** @class UnpackMS
-   ** @author Dominik Smith <d.smith@gsi.de>
+   ** @author Dominik Smith <d.smith@gsi.de>, Alex Bercuci <abercuci@niham.nipne.ro>
    ** @since 31 January 2023
-   ** @brief Unpack algorithm for TRD
+   ** @brief Unpack algorithm for TRD2D
    **/
+  template<std::uint8_t sys_ve>
   class UnpackMS : public UnpackMSBase<CbmTrdDigi, UnpackMonitorData, UnpackAuxData> {
 
    public:
     /** @brief Construct from parameters **/
-    UnpackMS(const UnpackPar& pars);
-
+    UnpackMS(const UnpackPar& /*pars*/) {}
 
     /** @brief Destructor **/
-    ~UnpackMS() override;
-
+    ~UnpackMS() override = default;
 
     /** @brief Algorithm execution
      ** @param  msContent  Microslice payload
@@ -152,25 +222,58 @@ namespace cbm::algo::trd2d
 
     /** @brief Finalize component (e.g. copy from temp buffers)  */
     std::vector<CbmTrdDigi> FinalizeComponent(MsContext& ctx) const;
-
-    // Constants
-    /** @brief Bytes per FASP frame stored in the microslices (32 bits words)
-   * - DATA WORD -
-   * ffff.ffdd dddd.dddd dddd.tttt ttta.cccc
-   * f - FASP id
-   * d - ADC signal
-   * t - time label inside epoch
-   * a - word type (1)
-   * c - channel id
-   * - EPOCH WORD -
-   * ffff.fftt tttt.tttt tttt.tttt ttta.cccc
-   * f - FASP id
-   * t - epoch index
-   * a - word type (0)
-   * c - channel id
-   */
-    static const std::uint8_t fBytesPerWord = 4;
   };
 
 
+  /** @class UnpackMS<kMess24>
+   ** @author Alex Bercuci <abercuci@niham.nipne.ro>
+   ** @since 15 January 2025
+   ** @brief Unpack algorithm specialization for TRD2D based on one-layered FEB design introduced in
+   * 06.2024 (https://indico.gsi.de/event/20885/attachments/49263/72236/TRD2D-CDR-FEB.pdf)
+   * - using different message format (\see \enum eMessageVersion) starting with kMess24
+   * - remove time buffering of digi per channel as a new 32 channels ADC is used in the FEE
+   **/
+  template<>
+  class UnpackMS<uint8_t(eMessageVersion::kMess24)> :
+    public UnpackMSBase<CbmTrdDigi, UnpackMonitorData, UnpackAuxData> {
+
+   public:
+    /** @brief Construct from parameters **/
+    UnpackMS(const UnpackPar& /*pars*/) {}
+
+    /** @brief Destructor **/
+    ~UnpackMS() override = default;
+
+
+    /** @brief Algorithm execution
+     ** @param  msContent  Microslice payload
+     ** @param  msDescr    Microslice descriptor
+     ** @param  tTimeslice Unix start time of timeslice [ns]
+     ** @return TRD digi data
+     **/
+    Result_t operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                        const uint64_t tTimeslice) const override;
+
+    /** @brief Set the parameter container
+     ** @param params Pointer to parameter container
+     **/
+    void SetParams(std::unique_ptr<UnpackPar> params) { fParams = *(std::move(params)); }
+
+   private:  // Types
+    struct MsContext {
+      UnpackMonitorData fMonitor;  ///< Container for monitoring data
+      std::array<std::vector<CbmTrdDigi>, NFASPMOD* NFASPCH> fRobDigi = {
+        {}};              ///> Buffered digi for each pad in one Epoch-ROB component
+      FaspMessage fMess;  ///< encapsulation of the FASP message.
+    };
+
+   private:  // members
+    bool pushDigis(std::vector<FaspMessage> messages, const uint64_t time, MsContext& ctx) const;
+
+    UnpackPar fParams = {};  ///< Parameter container
+
+    /** @brief Clock frequency of FASP in 0.08 GHz. HW also supports 0.04 GHz*/
+    static constexpr float fAsicClockFreq = 0.08;
+  };
+
 }  // namespace cbm::algo::trd2d
-- 
GitLab