From 368e7a5e58808f73773480cc2d61b245d47b5f35 Mon Sep 17 00:00:00 2001
From: Dominik Smith <d.smith@gsi.de>
Date: Tue, 23 Aug 2022 12:34:51 +0200
Subject: [PATCH] Added TOF unpacker in algo namespace. Modified CMakeLists to
 compile new TOF and T0 unpackers.

---
 algo/CMakeLists.txt                     |   6 +-
 algo/detectors/tof/TofReadoutConfig.cxx | 445 ++++++++++++++++++++++++
 algo/detectors/tof/TofReadoutConfig.h   | 103 ++++++
 algo/detectors/tof/UnpackTof.cxx        | 165 +++++++++
 algo/detectors/tof/UnpackTof.h          | 121 +++++++
 5 files changed, 839 insertions(+), 1 deletion(-)
 create mode 100644 algo/detectors/tof/TofReadoutConfig.cxx
 create mode 100644 algo/detectors/tof/TofReadoutConfig.h
 create mode 100644 algo/detectors/tof/UnpackTof.cxx
 create mode 100644 algo/detectors/tof/UnpackTof.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 6e80b666e2..4f7e36be31 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -11,7 +11,11 @@ set(SRCS
   detectors/much/MuchReadoutConfig.cxx
   detectors/much/UnpackMuch.cxx
   detectors/tof/HitFinderTof.cxx
-)
+  detectors/tof/TofReadoutConfig.cxx
+  detectors/tof/UnpackTof.cxx
+  detectors/t0/T0ReadoutConfig.cxx
+  detectors/t0/UnpackT0.cxx
+ )
 
 add_library(Algo SHARED ${SRCS})
 
diff --git a/algo/detectors/tof/TofReadoutConfig.cxx b/algo/detectors/tof/TofReadoutConfig.cxx
new file mode 100644
index 0000000000..ac108fa1a9
--- /dev/null
+++ b/algo/detectors/tof/TofReadoutConfig.cxx
@@ -0,0 +1,445 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#include "TofReadoutConfig.h"
+
+#include "CbmTofAddress.h"
+
+#include <Logger.h>
+
+#include <bitset>
+#include <iomanip>
+
+#include "gDpbMessv100.h"
+
+using namespace std;
+
+namespace cbm::algo
+{
+  // ---  Constructor  ------------------------------------------------------------------
+  TofReadoutConfig::TofReadoutConfig() { Init(); }
+  // ------------------------------------------------------------------------------------
+
+  // ---   Destructor   -----------------------------------------------------------------
+  TofReadoutConfig::~TofReadoutConfig() {}
+  // ------------------------------------------------------------------------------------
+
+  // ---   Equipment IDs   --------------------------------------------------------------
+  std::vector<uint16_t> TofReadoutConfig::GetEquipmentIds()
+  {
+    std::vector<uint16_t> result;
+    for (auto& entry : fReadoutMap)
+      result.push_back(entry.first);
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+  // ---   Number of elinks for a component / equipment   -------------------------------
+  size_t TofReadoutConfig::GetNumElinks(uint16_t equipmentId)
+  {
+    size_t result = 0;
+    auto it       = fReadoutMap.find(equipmentId);
+    if (it != fReadoutMap.end()) result = fReadoutMap[equipmentId].size();
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+
+  // ---  Mapping (equimentId, elink) -> address[channel]  ------------------------------
+  std::vector<uint32_t> TofReadoutConfig::Map(uint16_t equipmentId, uint16_t elinkId)
+  {
+    std::vector<uint32_t> result;
+    auto equipIter = fReadoutMap.find(equipmentId);
+    if (equipIter != fReadoutMap.end()) {
+      if (elinkId < equipIter->second.size()) { result = equipIter->second.at(elinkId); }
+    }
+    return result;
+  }
+  // ------------------------------------------------------------------------------------
+
+  void TofReadoutConfig::Init()
+  {
+    // This here refers to the mCBM 2022 setup.
+    // Taken from CbmMcbm2018TofPar in combination with macro/beamtime/mcbm2022/mTofCriParNickel.par
+
+    // Array to hold the unique IDs (equipment ID) for all TOF DPBs
+    uint16_t eqId[numComp] = {0xabc0, 0xabc1, 0xabc2, 0xabc3, 0xabc4, 0xabc5, 0xabc6, 0xabc7};
+
+    // Constructing the map (equipmentId, eLink, channel) -> (TOF address)
+    const uint32_t numChanPerComp = numChanPerAsic * numElinksPerComp;
+
+    // Constructs the fviRpcChUId array
+    BuildChannelsUidMap();
+
+    for (uint16_t comp = 0; comp < numComp; comp++) {
+
+      uint16_t equipment = eqId[comp];
+      fReadoutMap[equipment].resize(numElinksPerComp);
+
+      for (uint16_t elink = 0; elink < numElinksPerComp; elink++) {
+        fReadoutMap[equipment][elink].resize(numChanPerAsic);
+        const uint32_t asicId = ElinkIdxToGet4Idx(elink);
+
+        for (uint16_t channel = 0; channel < numChanPerAsic; channel++) {
+          const uint32_t febId        = (asicId / numAsicsPerFeb);
+          const uint32_t chanInFeb    = (asicId % numAsicsPerFeb) * numChanPerAsic + channel;
+          const uint32_t remappedChan = comp * numChanPerComp + febId * numChanPerFeb + Get4ChanToPadiChan(chanInFeb);
+          const uint32_t chanUId      = fviRpcChUId[remappedChan];
+          fReadoutMap[equipment][elink][channel] = chanUId;
+        }  //# channel
+      }    //# elink
+    }      //# component
+  }
+
+  int32_t TofReadoutConfig::ElinkIdxToGet4Idx(uint32_t elink)
+  {
+    if (gdpbv100::kuChipIdMergedEpoch == elink) return elink;
+    else if (elink < numElinksPerComp)
+      return elink2Asic[elink % numElinksPerCrob] + numElinksPerCrob * (elink / numElinksPerCrob);
+    else {
+      LOG(fatal) << "CbmMcbm2018TofPar::ElinkIdxToGet4Idx => Index out of bound, " << elink << " vs "
+                 << static_cast<uint32_t>(numElinksPerComp) << ", returning crazy value!";
+      return -1;
+    }
+  }
+  // -------------------------------------------------------------------------
+
+
+  // -------------------------------------------------------------------------
+  int32_t TofReadoutConfig::Get4ChanToPadiChan(uint32_t channelInFee)
+  {
+    if (channelInFee < numChanPerFeb) return asic2PadI[channelInFee];
+    else {
+      LOG(fatal) << "CbmMcbm2018TofPar::Get4ChanToPadiChan => Index out of bound, " << channelInFee << " vs "
+                 << static_cast<uint32_t>(numChanPerFeb) << ", returning crazy value!";
+      return -1;
+    }
+  }
+  // -------------------------------------------------------------------------
+
+  // -------------------------------------------------------------------------
+  void TofReadoutConfig::BuildChannelsUidMap()
+  {
+    uint32_t uNrOfGet4     = numComp * numFebsPerComp * numAsicsPerFeb;
+    uint32_t uNrOfChannels = uNrOfGet4 * numChanPerAsic;
+    fviRpcChUId.resize(uNrOfChannels);
+
+    uint32_t uCh = 0;
+    for (uint32_t uGbtx = 0; uGbtx < numCrob; ++uGbtx) {
+      uint32_t uCh0 = uCh;
+      switch (rpcType[uGbtx]) {
+        case 2:  // intended fall-through
+        case 0: {
+          // CBM modules
+          BuildChannelsUidMapCbm(uCh, uGbtx);
+          break;
+        }
+        case 1: {
+          // STAR eTOF  modules
+          BuildChannelsUidMapStar(uCh, uGbtx);
+          break;
+        }
+        case 78: {
+          // cern-20-gap + ceramic module
+          BuildChannelsUidMapCern(uCh, uGbtx);
+        }
+          [[fallthrough]];  // fall through is intended
+        case 8:             // ceramics
+        {
+          BuildChannelsUidMapCera(uCh, uGbtx);
+          break;
+        }
+        case 69:
+        case 6:  // Buc box
+        {
+          BuildChannelsUidMapBuc(uCh, uGbtx);
+          if (rpcType[uGbtx] == 6) break;
+        }
+          [[fallthrough]];
+        case 4:  // intended fallthrough
+          [[fallthrough]];
+        case 7: [[fallthrough]];
+        case 9:  // Star2 boxes
+        {
+          if (rpcType[uGbtx] == 69) uCh -= 80;
+          BuildChannelsUidMapStar2(uCh, uGbtx);
+          if (rpcType[uGbtx] == 69) uCh -= 80;
+          break;
+        }
+
+        case -1: {
+          LOG(info) << " Found unused GBTX link at uCh = " << uCh;
+          uCh += 160;
+          break;
+        }
+        default: {
+          LOG(error) << "Invalid Tof Type specifier for GBTx " << std::setw(2) << uGbtx << ": " << rpcType[uGbtx];
+        }
+      }  // switch (rpcType[uGbtx])
+      if ((int32_t)(uCh - uCh0) != numFebsPerComp * numAsicsPerFeb * numChanPerAsic / 2) {
+        LOG(fatal) << "Tof mapping error for Gbtx " << uGbtx << ",  diff = " << uCh - uCh0 << ", type "
+                   << rpcType[uGbtx];
+      }
+    }  // for (UInt_t uGbtx = 0; uGbtx < numCrob; ++uGbtx)
+  }
+  // -------------------------------------------------------------------------
+
+
+  // -------------------------------------------------------------------------
+  void TofReadoutConfig::BuildChannelsUidMapCbm(uint32_t& uCh, uint32_t uGbtx)
+  {
+    LOG(info) << " Map mTof box " << moduleId[uGbtx] << " at GBTX " << uGbtx << " -  uCh = " << uCh;
+    if (rpcSide[uGbtx] < 2) {  // mTof modules
+      LOG(debug) << " Map mTof box " << moduleId[uGbtx] << " at GBTX  -  uCh = " << uCh;
+      const int32_t RpcMap[5] = {4, 2, 0, 3, 1};
+      for (int32_t iRpc = 0; iRpc < numRpc[uGbtx]; iRpc++) {
+        int32_t iStrMax  = 32;
+        uint32_t uChNext = 1;
+
+        for (int32_t iStr = 0; iStr < iStrMax; iStr++) {
+          int32_t iStrMap = iStr;
+          int32_t iRpcMap = RpcMap[iRpc];
+
+          if (rpcSide[uGbtx] == 0) iStrMap = 31 - iStr;
+          if (moduleId[uGbtx] > -1)
+            fviRpcChUId[uCh] =
+              CbmTofAddress::GetUniqueAddress(moduleId[uGbtx], iRpcMap, iStrMap, rpcSide[uGbtx], rpcType[uGbtx]);
+          else
+            fviRpcChUId[uCh] = 0;
+          uCh += uChNext;
+        }  // for (int32_t iStr = 0; iStr < iStrMax; iStr++)
+      }    // for (int32_t iRpc = 0; iRpc < numRpc[uGbtx]; iRpc++)
+    }      // if (rpcSide[uGbtx] < 2)
+  }
+
+
+  // -------------------------------------------------------------------------
+  void TofReadoutConfig::BuildChannelsUidMapStar(uint32_t& uCh, uint32_t uGbtx)
+  {
+    if (rpcSide[uGbtx] < 2) {
+      // mTof modules
+      LOG(info) << "Start eTOF module side " << rpcSide[uGbtx] << " at " << uCh;
+      const int32_t RpcMap[3] = {0, 1, 2};
+      for (int32_t iRpc = 0; iRpc < numRpc[uGbtx]; iRpc++) {
+        int32_t iStrMax = 32;
+        int32_t uChNext = 1;
+
+        for (int32_t iStr = 0; iStr < iStrMax; iStr++) {
+          int32_t iStrMap = iStr;
+          int32_t iRpcMap = RpcMap[iRpc];
+
+          if (rpcSide[uGbtx] == 0) iStrMap = 31 - iStr;
+          if (moduleId[uGbtx] > -1)
+            fviRpcChUId[uCh] =
+              CbmTofAddress::GetUniqueAddress(moduleId[uGbtx], iRpcMap, iStrMap, rpcSide[uGbtx], rpcType[uGbtx]);
+          else
+            fviRpcChUId[uCh] = 0;
+          uCh += uChNext;
+        }
+      }
+    }
+    uCh += 64;
+  }
+
+  // -------------------------------------------------------------------------
+  void TofReadoutConfig::BuildChannelsUidMapCern(uint32_t& uCh, uint32_t)
+  {
+    LOG(info) << " Map CERN 20 gap  at GBTX  -  uCh = " << uCh;
+    // clang-format off
+  const int32_t StrMap[32] = {0,  1,  2,  3,  4,  31, 5,  6,  7,  30, 8,
+                            9,  10, 29, 11, 12, 13, 14, 28, 15, 16, 17,
+                            18, 27, 26, 25, 24, 23, 22, 21, 20, 19};
+    // clang-format on
+    int32_t iModuleId   = 0;
+    int32_t iModuleType = 7;
+    int32_t iRpcMap     = 0;
+    for (int32_t iFeet = 0; iFeet < 2; iFeet++) {
+      for (int32_t iStr = 0; iStr < 32; iStr++) {
+        int32_t iStrMap  = 31 - 12 - StrMap[iStr];
+        int32_t iSideMap = iFeet;
+        if (iStrMap < 20)
+          fviRpcChUId[uCh] = CbmTofAddress::GetUniqueAddress(iModuleId, iRpcMap, iStrMap, iSideMap, iModuleType);
+        else
+          fviRpcChUId[uCh] = 0;
+        uCh++;
+      }
+    }
+    LOG(info) << " Map end CERN 20 gap  at GBTX  -  uCh = " << uCh;
+  }
+
+  // -------------------------------------------------------------------------
+  void TofReadoutConfig::BuildChannelsUidMapCera(uint32_t& uCh, uint32_t)
+  {
+    int32_t iModuleId   = 0;
+    int32_t iModuleType = 8;
+    for (int32_t iRpc = 0; iRpc < 8; iRpc++) {
+      fviRpcChUId[uCh] = CbmTofAddress::GetUniqueAddress(iModuleId, 7 - iRpc, 0, 0, iModuleType);
+      uCh++;
+    }
+    uCh += (24 + 2 * 32);
+    LOG(info) << " Map end ceramics  box  at GBTX  -  uCh = " << uCh;
+  }
+
+
+  // -------------------------------------------------------------------------
+  void TofReadoutConfig::BuildChannelsUidMapStar2(uint32_t& uCh, uint32_t uGbtx)
+  {
+    LOG(info) << " Map Star2 box " << moduleId[uGbtx] << " at GBTX " << uGbtx << " -  uCh = " << uCh;
+    const int32_t iRpc[5]  = {1, -1, 1, 0, 0};
+    const int32_t iSide[5] = {1, -1, 0, 1, 0};
+    for (int32_t iFeet = 0; iFeet < 5; iFeet++) {
+      for (int32_t iStr = 0; iStr < 32; iStr++) {
+        int32_t iStrMap  = iStr;
+        int32_t iRpcMap  = iRpc[iFeet];
+        int32_t iSideMap = iSide[iFeet];
+        if (iSideMap == 0) iStrMap = 31 - iStr;
+        switch (rpcSide[uGbtx]) {
+          case 0:; break;
+          case 1:;
+            iRpcMap = 1 - iRpcMap;  // swap counters
+            break;
+          case 2:
+            switch (iFeet) {
+              case 1:
+                iRpcMap  = iRpc[4];
+                iSideMap = iSide[4];
+                iStrMap  = 31 - iStrMap;
+                break;
+              case 4:
+                iRpcMap  = iRpc[1];
+                iSideMap = iSide[1];
+                break;
+              default:;
+            }
+            break;
+          case 3:  // direct beam 20210524
+            switch (iFeet) {
+              case 0:
+                iRpcMap  = 0;
+                iSideMap = 0;
+                iStrMap  = iStr;
+                break;
+              case 1:
+                iRpcMap  = 0;
+                iSideMap = 1;
+                iStrMap  = 31 - iStr;
+                break;
+              default: iSideMap = -1;
+            }
+            break;
+        }
+        if (iSideMap > -1)
+          fviRpcChUId[uCh] =
+            CbmTofAddress::GetUniqueAddress(moduleId[uGbtx], iRpcMap, iStrMap, iSideMap, rpcType[uGbtx]);
+        else
+          fviRpcChUId[uCh] = 0;
+        uCh++;
+      }
+    }
+  }
+
+
+  // -------------------------------------------------------------------------
+  void TofReadoutConfig::BuildChannelsUidMapBuc(uint32_t& uCh, uint32_t uGbtx)
+  {
+    LOG(info) << " Map Buc box " << moduleId[uGbtx] << " at GBTX " << uGbtx << " -  uCh = " << uCh;
+
+    int32_t iModuleIdMap   = moduleId[uGbtx];
+    const int32_t iRpc[5]  = {0, -1, 0, 1, 1};
+    const int32_t iSide[5] = {1, -1, 0, 1, 0};
+    for (int32_t iFeet = 0; iFeet < 5; iFeet++) {
+      for (int32_t iStr = 0; iStr < 32; iStr++) {
+        int32_t iStrMap  = iStr;
+        int32_t iRpcMap  = iRpc[iFeet];
+        int32_t iSideMap = iSide[iFeet];
+        switch (rpcSide[uGbtx]) {
+          case 0:; break;
+          case 1:  // HD cosmic 2019, Buc2018, v18n
+            iStrMap = 31 - iStr;
+            iRpcMap = 1 - iRpcMap;
+            break;
+          case 2:  // v18m_cosmicHD
+            //   iStrMap=31-iStr;
+            iSideMap = 1 - iSideMap;
+            break;
+          case 3:
+            iStrMap  = 31 - iStr;
+            iRpcMap  = 1 - iRpcMap;
+            iSideMap = 1 - iSideMap;
+            break;
+          case 4:  // HD cosmic 2019, Buc2018, v18o
+            iRpcMap = 1 - iRpcMap;
+            break;
+          case 5:  // HD cosmic 2020, Buc2018, v20a
+            iStrMap = 31 - iStr;
+            break;
+          case 6:  //BUC special
+          {
+            switch (moduleId[uGbtx]) {
+              case 0: iRpcMap = 0; break;
+              case 1: iRpcMap = 1; break;
+            }
+            if (iFeet % 2 == 1) iModuleIdMap = 1;
+            else
+              iModuleIdMap = 0;
+
+            switch (iFeet) {
+              case 0:
+              case 3: iSideMap = 0; break;
+              case 1:
+              case 2: iSideMap = 1; break;
+            }
+          } break;
+
+          case 7: {
+            // clang-format off
+          const int32_t iChMap[160]={
+          124, 125, 126, 127,  12,  13,  14,  15,   4,   5,   6,   7,  28,  29,  30,  31, 120, 121, 122, 123,   8,  9,   10,  11, 104, 105, 106, 107, 108, 109, 110, 111,
+           36,  37,  38,  39,  52,  53,  54,  55,  60,  61,  62,  63, 128, 129, 130, 131,  40,  41,  42,  43, 148, 149, 150, 151,  56,  57,  58,  59, 132, 133, 134, 135,
+          136, 137, 138, 139, 140, 141, 142, 143,  96,  97,  98,  99,  64,  65,  66,  67, 100, 101, 102, 103,  84,  85,  86,  87, 152, 153, 154, 155,  68,  69,  70,  71,
+          156, 157, 158, 159, 144, 145, 146, 147,  44,  45,  46,  47,  76,  77,  78,  79,  48,  49,  50,  51,  20,  21,  22,  23,  32,  33,  34,  35, 116, 117, 118, 119,
+           75,  74,  73,  72,  92,  93,  94,  95,  16,  17,  18,  19,  80,  81,  82,  83, 115, 114, 113, 112,  24,  25,  26,  27,  88,  89,  90,  91,   0,   1,   2,   3
+          };
+            // clang-format on
+            int32_t iInd = iFeet * 32 + iStr;
+            int32_t i    = 0;
+            for (; i < 160; i++)
+              if (iInd == iChMap[i]) break;
+            iStrMap          = i % 32;
+            int32_t iFeetInd = (i - iStrMap) / 32;
+            switch (iFeetInd) {
+              case 0:
+                iRpcMap  = 0;
+                iSideMap = 1;
+                break;
+              case 1:
+                iRpcMap  = 1;
+                iSideMap = 1;
+                break;
+              case 2:
+                iRpcMap  = 1;
+                iSideMap = 0;
+                break;
+              case 3:
+                iRpcMap  = 0;
+                iSideMap = 0;
+                break;
+              case 4: iSideMap = -1; break;
+            }
+            iModuleIdMap = moduleId[uGbtx];
+          } break;
+          default:;
+        }  // switch (rpcSide[uGbtx])
+        if (iSideMap > -1)
+          fviRpcChUId[uCh] = CbmTofAddress::GetUniqueAddress(iModuleIdMap, iRpcMap, iStrMap, iSideMap, rpcType[uGbtx]);
+        else
+          fviRpcChUId[uCh] = 0;
+
+        uCh++;
+      }  // for (int32_t iStr = 0; iStr < 32; iStr++)
+    }    // for (int32_t iFeet = 0; iFeet < 5; iFeet++)
+  }
+  // -------------------------------------------------------------------------
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/tof/TofReadoutConfig.h b/algo/detectors/tof/TofReadoutConfig.h
new file mode 100644
index 0000000000..30b40104fe
--- /dev/null
+++ b/algo/detectors/tof/TofReadoutConfig.h
@@ -0,0 +1,103 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#ifndef ALGO_DETECTORS_TOF_TOFREADOUTCONFIG_H
+#define ALGO_DETECTORS_TOF_TOFREADOUTCONFIG_H
+
+#include <cstdint>
+#include <map>
+#include <vector>
+
+namespace cbm::algo
+{
+  class TofReadoutConfig {
+
+  public:
+    /** @brief Constructor **/
+    TofReadoutConfig();
+
+    /** @brief Destructor **/
+    virtual ~TofReadoutConfig();
+
+    /** @brief Equipment in the configuration
+     ** @return Vector of equipment IDs
+     **/
+    std::vector<uint16_t> GetEquipmentIds();
+
+    /** @brief Number of elinks of a component
+     ** @param Equipment ID
+     ** @return Number of elinks
+     **/
+    size_t GetNumElinks(uint16_t equipmentId);
+
+    /** @brief API: Mapping from component and elink to addresses per channel
+     ** @param equipId     Equipment identifier (component)
+     ** @param elink       Elink number within component
+     ** @return Vector of TOF addresses, indexed via channel number
+     */
+    std::vector<uint32_t> Map(uint16_t equipId, uint16_t elink);
+
+  private:
+    // --- TOF readout map
+    // --- Map index: (equipment, elink, channel), map value: (TOF address)
+    std::map<uint16_t, std::vector<std::vector<uint32_t>>> fReadoutMap = {};
+
+    /** @brief Initialisation of readout map **/
+    void Init();
+
+    /// Mapping to eLink to ASIC number within DPB. Mapping is the same for each DPB.
+    int32_t ElinkIdxToGet4Idx(uint32_t elink);
+    int32_t Get4ChanToPadiChan(uint32_t channelInFee);
+
+    /// Constants
+
+    /// Taken from mTofCriParNickel_withBmon.par
+    static const uint16_t numComp        = 8;   // Total number of TOF DPBs in system
+    static const uint32_t numFebsPerComp = 10;  // Number of FEEs which are connected to one GDPB
+    static const uint32_t numAsicsPerFeb = 8;   // Number of ASICs connected in each FEB for TOF
+    static const uint32_t numChanPerAsic = 4;   // Number of channels in each ASIC
+    static const uint32_t numCrob        = 16;  // Total number of Gbtx links
+
+    // Module Identifier connected to Gbtx link, has to match geometry
+    const int32_t moduleId[numCrob] = {0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 4, 4, 0 - 1, 0, 0};
+
+    // type of Rpcs connected to Gbtx link
+    const int32_t rpcType[numCrob] = {0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 0, 9, 9, 6, 9};
+
+    // side of Rpcs connected to Gbtx link, i.e. 0 or 1
+    const int32_t rpcSide[numCrob] = {1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 0, 8, 0};
+
+    // number of Rpcs connected to Gbtx link, i.e. 3 or 5
+    const int32_t numRpc[numCrob] = {5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5};
+
+    //// Taken from CbmMcbm2018TofPar.h  (fixed numCrobPerComp)
+    static const uint32_t numCrobPerComp   = 2;  // Number of CROBs possible per DPB
+    static const uint32_t numFebsPerCrob   = 5;  // Number of FEBs  connected to each CROB for mTof 2019
+    static const uint32_t numChanPerFeb    = numChanPerAsic * numAsicsPerFeb;
+    static const uint32_t numElinksPerCrob = numAsicsPerFeb * numFebsPerCrob;
+    static const uint32_t numElinksPerComp = numElinksPerCrob * numCrobPerComp;
+
+    // Mapping to eLink to ASIC number within CROB. Mapping is the same for each CROB.
+    const uint32_t elink2Asic[numElinksPerCrob] = {27, 2,  7,  3,  31, 26, 30, 1,  33, 37, 32, 13, 9,  14,
+                                                   10, 15, 17, 21, 16, 35, 34, 38, 25, 24, 0,  6,  20, 23,
+                                                   18, 22, 28, 4,  29, 5,  19, 36, 39, 8,  12, 11};
+    // Mapping in Readout chain PCBs
+    const uint32_t asic2PadI[numChanPerFeb] = {
+      3,  2,  1,  0,  7,  6,  5,  4,  11, 10, 9,  8,  15, 14, 13, 12,
+      19, 18, 17, 16, 23, 22, 21, 20, 27, 26, 25, 24, 31, 30, 29, 28};  //! Map from GET4 channel to PADI channel
+
+    std::vector<int32_t> fviRpcChUId = {};  // UID/address for each channel, build from type, side and module
+
+    void BuildChannelsUidMap();
+    void BuildChannelsUidMapCbm(uint32_t& uCh, uint32_t uGbtx);
+    void BuildChannelsUidMapStar(uint32_t& uCh, uint32_t uGbtx);
+    void BuildChannelsUidMapCern(uint32_t& uCh, uint32_t uGbtx);
+    void BuildChannelsUidMapCera(uint32_t& uCh, uint32_t uGbtx);
+    void BuildChannelsUidMapStar2(uint32_t& uCh, uint32_t uGbtx);
+    void BuildChannelsUidMapBuc(uint32_t& uCh, uint32_t uGbtx);
+  };
+
+} /* namespace cbm::algo */
+
+#endif  //ALGO_DETECTORS_TOF_TOFREADOUTCONFIG_H
diff --git a/algo/detectors/tof/UnpackTof.cxx b/algo/detectors/tof/UnpackTof.cxx
new file mode 100644
index 0000000000..fa12645f2e
--- /dev/null
+++ b/algo/detectors/tof/UnpackTof.cxx
@@ -0,0 +1,165 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#include "UnpackTof.h"
+
+#include <cassert>
+#include <utility>
+#include <vector>
+
+#include <cmath>
+
+using std::unique_ptr;
+using std::vector;
+
+namespace cbm::algo
+{
+
+  // ----   Algorithm execution   ---------------------------------------------
+  UnpackTof::resultType UnpackTof::operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                                              const uint64_t tTimeslice)
+  {
+
+    // --- Output data
+    resultType result = {};
+
+    // --- Current Timeslice start time in epoch units. Note that it is always a multiple of epochs
+    // --- and the epoch is a multiple of ns.
+    fCurrentTsTime = static_cast<uint64_t>(tTimeslice / critof001::kuEpochInNs) % critof001::kulEpochCycleEp;
+
+    // --- Number of messages in microslice
+    auto msSize = msDescr.size;
+    if (msSize % sizeof(critof001::Message) != 0) {
+      result.second.fNumErrInvalidMsSize++;
+      return result;
+    }
+    const uint32_t numMessages = msSize / sizeof(critof001::Message);
+    if (numMessages < 2) {
+      result.second.fNumErrInvalidMsSize++;
+      return result;
+    }
+
+    // --- Interpret MS content as sequence of Critof messages
+    auto message = reinterpret_cast<const critof001::Message*>(msContent);
+
+    // --- The first message in the MS is expected to be of type EPOCH.
+    if (message[0].getMessageType() != critof001::MSG_EPOCH) {
+      result.second.fNumErrInvalidFirstMessage++;
+      return result;
+    }
+
+    {  // --- Check that first epoch matches with the microslice index
+      const uint64_t msStartEpoch =
+        static_cast<uint64_t>(msDescr.idx / critof001::kuEpochInNs) % critof001::kulEpochCycleEp;
+      if (message[0].getGdpbEpEpochNb() != msStartEpoch) {
+        result.second.fNumErrInvalidStartEpoch++;
+        return result;
+      }
+    }
+
+    // --- The last message in the MS is expected to be EndOfMs.
+    if (!message[numMessages - 1].isEndOfMs()) {
+      result.second.fNumErrInvalidLastMessage++;
+      return result;
+    }
+    // Check if last message is "EndOfMs"!! Maybe loop to messageNr < numMessages - 1
+
+    // --- Message loop
+    for (uint32_t messageNr = 0; messageNr < numMessages; messageNr++) {
+
+      // --- Action depending on message type
+      switch (message[messageNr].getMessageType()) {
+
+        case critof001::MSG_HIT: {
+          ProcessHitMessage(message[messageNr], result.first, result.second);
+          break;
+        }
+        case critof001::MSG_EPOCH: {
+          ProcessEpochMessage(message[messageNr]);
+          break;
+        }
+        case critof001::MSG_SLOWC: {
+          // Fill error
+          break;
+        }
+        case critof001::MSG_SYST: {
+          // Fill error
+          break;
+        }
+        default: {
+          result.second.fNumNonHitOrTsbMessage++;
+          break;
+        }
+      }  //? Message type
+    }    //# Messages
+
+    return result;
+  }
+  // --------------------------------------------------------------------------
+
+
+  // -----   Process hit message   --------------------------------------------
+  inline void UnpackTof::ProcessHitMessage(const critof001::Message& message, vector<CbmTofDigi>& digiVec,
+                                           UnpackTofMonitorData& monitor) const
+  {
+    // --- Check eLink and get parameters
+    const uint32_t elink = message.getGet4Idx();
+    if (elink >= fParams.fElinkParams.size()) {
+      monitor.fNumErrElinkOutOfRange++;
+      return;
+    }
+    const UnpackTofElinkPar& elinkPar = fParams.fElinkParams.at(elink);
+
+    const uint32_t channel    = message.getGdpbHitChanId();
+    const uint32_t channelUId = (elinkPar.fChannelUId)[channel];
+
+    double messageTime  = message.getMsgFullTimeD(fCurrentEpochInTs) - elinkPar.fTimeOffset;
+    const double charge = (double) message.getGdpbHit32Tot();  //cast from uint32_t
+
+    {  // weird address hack   (probably should not be in final version)
+      std::unique_ptr<CbmTofDigi> digi(new CbmTofDigi(channelUId, messageTime, charge));
+      int iSmType = 8;
+      int iSm     = -1;
+      int iRpc    = 0;
+      int iDetId  = 0;
+      if (digi->GetType() == 6 && digi->GetRpc() == 1) {
+        switch ((int) (digi->GetChannel() * 2 + digi->GetSide())) {
+          case 62:  //800
+            iSm = 0;
+            break;
+          case 46:  //810
+            iSm = 1;
+            break;
+          default:;
+        }
+        if (iSm > -1) {
+          iDetId = CbmTofAddress::GetUniqueAddress(iSm, iRpc, 0, 0, iSmType);
+          digi->SetAddress(iDetId);
+        }
+      }
+      if (digi) digiVec.emplace_back(*std::move(digi));
+    }  //special remapping end
+
+    // --- Create output digi
+    //digiVec.emplace_back(channelUId, messageTime, charge);  ((restore this))
+  }
+  // --------------------------------------------------------------------------
+
+
+  // -----   Process an epoch message   ---------------------------------------
+  inline void UnpackTof::ProcessEpochMessage(const critof001::Message& message)
+  {
+    const uint64_t epoch = message.getGdpbEpEpochNb();
+
+    // --- Calculate epoch relative to timeslice start time; correct for epoch cycles
+    if (fCurrentTsTime <= epoch) { fCurrentEpochInTs = epoch - fCurrentTsTime; }
+    else {
+      fCurrentEpochInTs = epoch + critof001::kulEpochCycleEp - fCurrentTsTime;
+    }
+    //Problem if MS spans multiple epoch cycles?
+  }
+  // --------------------------------------------------------------------------
+
+
+} /* namespace cbm::algo */
diff --git a/algo/detectors/tof/UnpackTof.h b/algo/detectors/tof/UnpackTof.h
new file mode 100644
index 0000000000..2710dd8619
--- /dev/null
+++ b/algo/detectors/tof/UnpackTof.h
@@ -0,0 +1,121 @@
+/* Copyright (C) 2022 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Dominik Smith [committer] */
+
+#ifndef CBM_ALGO_UNPACKTOF_H
+#define CBM_ALGO_UNPACKTOF_H 1
+
+
+#include "CbmTofDigi.h"
+
+#include "MicrosliceDescriptor.hpp"
+#include "Timeslice.hpp"
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "CriGet4Mess001.h"
+
+namespace cbm::algo
+{
+
+  /** @struct UnpackTofElinkPar
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief TOF Unpacking parameters for one eLink / ASIC
+   **/
+  struct UnpackTofElinkPar {
+    std::vector<uint32_t> fChannelUId;  ///< CbmTofAddress for different channels
+    uint64_t fTimeOffset = 0.;          ///< Time calibration parameter
+  };
+
+  /** @struct UnpackTofPar
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief Parameters required for the STS unpacking (specific to one component)
+   **/
+  struct UnpackTofPar {
+    uint32_t fNumChansPerAsic                   = 0;   ///< Number of channels per ASIC
+    uint32_t fNumAsicsPerModule                 = 0;   ///< Number of ASICS per module
+    std::vector<UnpackTofElinkPar> fElinkParams = {};  ///< Parameters for each eLink
+  };
+
+  /** @struct UnpackTofMoni
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 2 December 2021
+   ** @brief Monitoring data for STS unpacking
+   **/
+  struct UnpackTofMonitorData {
+    uint32_t fNumNonHitOrTsbMessage     = 0;
+    uint32_t fNumErrElinkOutOfRange     = 0;  ///< Elink not contained in parameters
+    uint32_t fNumErrInvalidFirstMessage = 0;  ///< First message is not EPOCH
+    uint32_t fNumErrInvalidLastMessage  = 0;  ///< Last message is not EndOfMs
+    uint32_t fNumErrInvalidMsSize       = 0;  ///< Microslice size is not multiple of message size
+    uint32_t fNumErrTimestampOverflow   = 0;  ///< Overflow in 64 bit time stamp
+    uint32_t fNumErrInvalidStartEpoch   = 0;  ///< Microslice index doesn't match first epoch
+    bool HasErrors()
+    {
+      uint32_t numErrors = fNumNonHitOrTsbMessage + fNumErrElinkOutOfRange + fNumErrInvalidFirstMessage
+                           + fNumErrInvalidLastMessage + fNumErrInvalidMsSize + fNumErrTimestampOverflow
+                           + fNumErrInvalidStartEpoch;
+      return (numErrors > 0 ? true : false);
+    }
+  };
+
+  /** @class UnpackTof
+   ** @author Pierre-Alain Loizeau <p.-a.loizeau@gsi.de>
+   ** @author Volker Friese <v.friese@gsi.de>
+   ** @since 25 November 2021
+   ** @brief Unpack algorithm for STS
+   **/
+  class UnpackTof {
+
+  public:
+    typedef std::pair<std::vector<CbmTofDigi>, UnpackTofMonitorData> resultType;
+
+    /** @brief Default constructor **/
+    UnpackTof() {};
+
+    /** @brief Destructor **/
+    ~UnpackTof() {};
+
+    /** @brief Algorithm execution
+     ** @param  msContent  Microslice payload
+     ** @param  msDescr    Microslice descriptor
+     ** @param  tTimeslice Unix start time of timeslice [ns]
+     ** @return STS digi data
+     **/
+    resultType operator()(const uint8_t* msContent, const fles::MicrosliceDescriptor& msDescr,
+                          const uint64_t tTimeslice);
+
+    /** @brief Set the parameter container
+     ** @param params Pointer to parameter container
+     **/
+    void SetParams(std::unique_ptr<UnpackTofPar> params) { fParams = *(std::move(params)); }
+
+  private:  // methods
+    /** @brief Process a hit message
+     ** @param message SMX message (32-bit word)
+     ** @param digiVec Vector to append the created digi to
+     ** @param monitor Reference to monitor object
+     **/
+    void ProcessHitMessage(const critof001::Message& message, std::vector<CbmTofDigi>& digiVec,
+                           UnpackTofMonitorData& monitor) const;
+
+    /** @brief Process an epoch message 
+     ** @param message SMX message (32-bit word)
+     **/
+    void ProcessEpochMessage(const critof001::Message& message);
+
+  private:                            // members
+    uint64_t fCurrentTsTime    = 0;   ///< Unix time of timeslice in units of epoch length
+    uint32_t fCurrentEpochInTs = 0;   ///< Current epoch number relative to timeslice start epoch
+    UnpackTofPar fParams       = {};  ///< Parameter container
+  };
+
+} /* namespace cbm::algo */
+
+#endif /* CBM_ALGO_UNPACKTOF_H */
-- 
GitLab