diff --git a/.gitattributes b/.gitattributes
index 6cf967cf006dee546047ab924bfb494a29d1e8d1..acf3ec92dd9bf76897b6449376a2c0530f28df04 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -10,4 +10,3 @@
 *.index filter=lfs diff=lfs merge=lfs -text
 *.pb filter=lfs diff=lfs merge=lfs -text
 *.data-* filter=lfs diff=lfs merge=lfs -text
-
diff --git a/models/nnlf_hop_temporal_model_float.sadl b/models/nnlf_hop_temporal_model_float.sadl
new file mode 100644
index 0000000000000000000000000000000000000000..1388fd9a35fa598fb8136b3cb2fdf21120c8bd9d
--- /dev/null
+++ b/models/nnlf_hop_temporal_model_float.sadl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b70e1d808e2eeb01760691c0f1885e80191cef13a3363b062bb1f86939a009a8
+size 6166219
diff --git a/models/nnlf_hop_temporal_model_int16.sadl b/models/nnlf_hop_temporal_model_int16.sadl
new file mode 100644
index 0000000000000000000000000000000000000000..914f483f0d50072cbc003f2bb28be54db954e2af
--- /dev/null
+++ b/models/nnlf_hop_temporal_model_int16.sadl
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:ba3b4e74166936eba83cf57e566a004c34ff555fca42e918d4a972d5b09daac6
+size 3086941
diff --git a/source/App/DecoderApp/DecApp.cpp b/source/App/DecoderApp/DecApp.cpp
index d8667b8969a43fd55bb8e06c32ee67301f3a7628..7a904ade7c279c32955b2c737e3180e301356e0b 100644
--- a/source/App/DecoderApp/DecApp.cpp
+++ b/source/App/DecoderApp/DecApp.cpp
@@ -651,6 +651,9 @@ void DecApp::xCreateDecLib()
 #if NN_HOP_UNIFIED_FORCE_USE
   m_cDecLib.setNnlfHopOption(m_nnlfHopOption);
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  m_cDecLib.setNnlfHopTemporalModelName(m_nnlfHopTemporalModelName);
+#endif
 #endif
 #if NN_FILTERING_SET_0
   m_cDecLib.setModelPath(m_ModelPath);
diff --git a/source/App/DecoderApp/DecAppCfg.cpp b/source/App/DecoderApp/DecAppCfg.cpp
index 7910efd5810c63c7aa0c365d3ddd9e4a90040659..1ac419fbc078a653aecfb24094b8b932c94b5f02 100644
--- a/source/App/DecoderApp/DecAppCfg.cpp
+++ b/source/App/DecoderApp/DecAppCfg.cpp
@@ -85,6 +85,9 @@ bool DecAppCfg::parseCfg( int argc, char* argv[] )
 #if NN_HOP_UNIFIED_FORCE_USE
   ( "NnlfHopDebugOption",       m_nnlfHopOption,  0, "Option used to debug stage 1 model. 0: default, 1: apply only on I slice, 2: apply on all slices using I type as input"  )
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  ("NnlfHopTemporalModelName",  m_nnlfHopTemporalModelName,            string("models/nnlf_hop_temporal_model_int16.sadl"), "HOP temporal loop filter model name\n")
+#endif
 #endif
 
 
diff --git a/source/App/DecoderApp/DecAppCfg.h b/source/App/DecoderApp/DecAppCfg.h
index 914b61207f0fd696f2137f5d505919fbaa8ddd5d..72f8280ab0440473606ee40e1f06318c8a1b8ed4 100644
--- a/source/App/DecoderApp/DecAppCfg.h
+++ b/source/App/DecoderApp/DecAppCfg.h
@@ -63,6 +63,9 @@ protected:
 #if NN_HOP_UNIFIED_FORCE_USE
   int           m_nnlfHopOption;
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  std::string   m_nnlfHopTemporalModelName;             ///<nnlf temporal hop model
+#endif
 #endif
 #if NN_FILTERING_SET_0
   std::string   m_ModelPath;                            ///< model path
diff --git a/source/App/EncoderApp/EncApp.cpp b/source/App/EncoderApp/EncApp.cpp
index 6b488f8b7eddc24692eecef6b65d2574ef937e5c..fbcf2b4e2b4b067381da8e565cf97a2c0b34c75a 100644
--- a/source/App/EncoderApp/EncApp.cpp
+++ b/source/App/EncoderApp/EncApp.cpp
@@ -1123,6 +1123,10 @@ void EncApp::xInitLibCfg()
 #if NN_HOP_UNIFIED_FORCE_USE
   m_cEncLib.setNnlfHopOption                                     (m_nnlfHopOption);
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  m_cEncLib.setUseNnlfHopTemporalFiltering                       (m_nnlfHopTemporalFiltering);
+  m_cEncLib.setNnlfHopTemporalModelName                          (m_nnlfHopTemporalModelName);
+#endif
 #endif
 
 #if NN_FILTERING_SET_0
diff --git a/source/App/EncoderApp/EncAppCfg.cpp b/source/App/EncoderApp/EncAppCfg.cpp
index 97d6c13c0a85fa918105dbf95548c7b2f9f3ce25..bdc72c7d2774108f59c72b79395580001ce65d64 100644
--- a/source/App/EncoderApp/EncAppCfg.cpp
+++ b/source/App/EncoderApp/EncAppCfg.cpp
@@ -1163,13 +1163,17 @@ bool EncAppCfg::parseCfg( int argc, char* argv[] )
 #endif
 #if NN_HOP_UNIFIED
   ( "NnlfHop",                                        m_nnlfHop,                                         true, "High operation point (HOP) of NN-based loop filter"  )
-  ( "NnlfHOPBlockSize",                               m_nnlfHopBlockSize,                                128u, "Base inference size of NN-based in-loop filter for HOP")
+  ( "NnlfHopBlockSize",                               m_nnlfHopBlockSize,                                128u, "Base inference size of NN-based in-loop filter for HOP")
   ( "NnlfHopInfSizeExt",                              m_nnlfHopInfSizeExt,                                 8u, "Extension of inference size of HOP loop filter"      )
   ( "NnlfHopMaxNumPrms",                              m_nnlfHopMaxNumPrms,                                 2u, "Number of conditional parameters of HOP loop filter" )
   ( "NnlfHopModelName",                               m_nnlfHopModelName, string("models/nnlf_hop_model_int16.sadl"), "HOP loop filter model name"                   )
 #if NN_HOP_UNIFIED_FORCE_USE
   ( "NnlfHopDebugOption",                             m_nnlfHopOption,                                      0, "Option used to debug stage 1 model. 0: default, 1: apply only on I slice, 2: apply on all slices using I type as input"  )
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  ( "NnlfHopTemporalFiltering",                       m_nnlfHopTemporalFiltering,                       false, "Use HOP temporal filtering"                          )
+  ( "NnlfHopTemporalModelName",                       m_nnlfHopTemporalModelName, string("models/nnlf_hop_temporal_model_int16.sadl"), "HOP temporal loop filter model name")
+#endif
 #endif
   ("SAO",                                             m_bUseSAO,                                         true, "Enable Sample Adaptive Offset")
   ("TestSAODisableAtPictureLevel",                    m_bTestSAODisableAtPictureLevel,                  false, "Enables the testing of disabling SAO at the picture level after having analysed all blocks")
@@ -3172,7 +3176,13 @@ bool EncAppCfg::xCheckParameter()
   {
     xConfirmPara( m_ccalf, "CCALF cannot be enabled when ALF is disabled" );
   }
-
+    
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  if (!m_nnlfHop)
+  {
+    xConfirmPara( m_nnlfHopTemporalFiltering, "Temporal HOP cannot be enabled when HOP is disabled" );
+  }
+#endif
 
   xConfirmPara( m_iSourceWidth  % SPS::getWinUnitX(m_chromaFormatIDC) != 0, "Picture width must be an integer multiple of the specified chroma subsampling");
   xConfirmPara( m_iSourceHeight % SPS::getWinUnitY(m_chromaFormatIDC) != 0, "Picture height must be an integer multiple of the specified chroma subsampling");
@@ -4270,7 +4280,12 @@ void EncAppCfg::xPrintParameter()
 #endif
 #if NN_HOP_UNIFIED
   msg(VERBOSE, "NnlfHop:%d ",    m_nnlfHop ? 1 : 0);
-  msg(VERBOSE, "NnlfHopOption:%d ",    m_nnlfHopOption);
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  msg(VERBOSE, "NnlfHopTemporalFiltering:%d ",    m_nnlfHopTemporalFiltering ? 1 : 0);
+#endif
+#if NN_HOP_UNIFIED_FORCE_USE
+  msg(VERBOSE, "NnlfHopDebugOption:%d ",    m_nnlfHopOption);
+#endif
 #endif
 #if JVET_AB0068_AC0328_NNLF_RDO
   msg( VERBOSE, "EncNnlfOpt:%d ", m_encNnlfOpt ? 1 : 0);
diff --git a/source/App/EncoderApp/EncAppCfg.h b/source/App/EncoderApp/EncAppCfg.h
index 720a74d64aca164248239af9b6606e3ef886daf9..1b60d4dda60670ff4760a472fb2becbf59f285b2 100644
--- a/source/App/EncoderApp/EncAppCfg.h
+++ b/source/App/EncoderApp/EncAppCfg.h
@@ -763,6 +763,10 @@ protected:
 #if NN_HOP_UNIFIED_FORCE_USE
   int         m_nnlfHopOption;
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  bool        m_nnlfHopTemporalFiltering;
+  std::string m_nnlfHopTemporalModelName;                     ///<nnlf temporal hop model
+#endif
 #endif
 
 #if NN_FILTERING_SET_0
diff --git a/source/Lib/CommonLib/NNFilterHOP.cpp b/source/Lib/CommonLib/NNFilterHOP.cpp
index abe37e7231ffc6888a5e222e9a54ba299f8a2383..fc2f9ecd280c170ed56ad997f0654ad7d355c430 100644
--- a/source/Lib/CommonLib/NNFilterHOP.cpp
+++ b/source/Lib/CommonLib/NNFilterHOP.cpp
@@ -55,7 +55,20 @@ struct Input
     nbInputs
   };
 };
-
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+struct InputTemporal
+{
+  enum
+  {
+    Rec = 0,
+    Pred,
+    List0,
+    List1,
+    QPbase,
+    nbInputsTemporal
+  };
+};
+#endif
 void NNFilterHOP::init(const std::string &filename, int picWidth, int picHeight, ChromaFormat format, int prmNum)
 {
   ifstream file(filename, ios::binary);
@@ -68,6 +81,7 @@ void NNFilterHOP::init(const std::string &filename, int picWidth, int picHeight,
       exit(-1);
     }
   }
+
   // prepare inputs
   m_inputs.resize(Input::nbInputs);
   resizeInputs(defaultInputSize + defaultBlockExt * 2, defaultInputSize + defaultBlockExt * 2);
@@ -91,7 +105,24 @@ void NNFilterHOP::init(const std::string &filename, int picWidth, int picHeight,
     }
   }
 }
-
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+void NNFilterHOP::initTemporal(const std::string &filename)
+{
+  ifstream file(filename, ios::binary);
+  if (!m_modelTemporal)
+  {
+    m_modelTemporal.reset(new sadl::Model<TypeSadlHOP>());
+    if (!m_modelTemporal->load(file))
+    {
+      cerr << "[ERROR] issue loading temporal model NNFilterHOP " << filename << endl;
+      exit(-1);
+    }
+  }
+  // prepare inputs
+  m_inputsTemporal.resize(InputTemporal::nbInputsTemporal);
+  resizeInputsTemporal(defaultInputSize + defaultBlockExt * 2, defaultInputSize + defaultBlockExt * 2);
+}
+#endif
 void NNFilterHOP::destroy()
 {
   for (int i = 0; i < (int)m_filtered.size(); i++)
@@ -132,8 +163,36 @@ void NNFilterHOP::resizeInputs(int width, int height)
     exit(-1);
   }
   m_input_quantizer = m_model->getInputsTemplate()[0].quantizer;   // assume all image inputs have same quantizer
+
 }
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+// default is square block + extension
+void NNFilterHOP::resizeInputsTemporal(int width, int height) // assume using same quantizer as the primary model
+{
+  int sizeW = width;
+  int sizeH = height;
+  if (sizeH == m_blocksizeTemporal[0] && sizeW == m_blocksizeTemporal[1])
+  {
+    return;
+  }
+  m_blocksizeTemporal[0] = sizeH;
+  m_blocksizeTemporal[1] = sizeW;
+  // note: later QP inputs can be optimized to avoid some duplicate computation with a 1x1 input
+  m_inputsTemporal[InputTemporal::Rec].resize(sadl::Dimensions({ 1, sizeH, sizeW, 1 }));
+  m_inputsTemporal[InputTemporal::Pred].resize(sadl::Dimensions({ 1, sizeH, sizeW, 1 }));
+  m_inputsTemporal[InputTemporal::List0].resize(sadl::Dimensions({ 1, sizeH, sizeW, 1 }));
+  m_inputsTemporal[InputTemporal::List1].resize(sadl::Dimensions({ 1, sizeH, sizeW, 1 }));
+  m_inputsTemporal[InputTemporal::QPbase].resize(sadl::Dimensions({ 1, sizeH, sizeW, 1 }));
+
+  if (!m_modelTemporal->init(m_inputsTemporal))
+  {
+    cerr << "[ERROR] issue init temporal model NNFilterHOP " << endl;
+    exit(-1);
+  }
+  m_input_quantizer_temporal = m_modelTemporal->getInputsTemplate()[0].quantizer;   // assume all image inputs have same quantizer
 
+}
+#endif
 void roundToOutputBitdepth(const PelUnitBuf &src, PelUnitBuf &dst, const ClpRngs &clpRngs)
 {
   for (int c = 0; c < MAX_NUM_COMPONENT; c++)
@@ -233,7 +292,76 @@ static void extractOutputs(const Picture &pic, sadl::Model<T> &m, PelUnitBuf &bu
     }
   }
 }
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+// bufDst: temporary buffer to store results
+// inferArea: area used for inference (include extension)
+template<typename T>
+static void extractOutputsTemporal(const Picture &pic, sadl::Model<T> &m, PelUnitBuf &bufDst, UnitArea inferArea, int extLeft,
+                           int extRight, int extTop, int extBottom)
+{
+  const int log2InputBitdepth = pic.cs->slice->clpRng(COMPONENT_Y).bd;   // internal bitdepth
+  auto      output            = m.result(0);
+  const int q_output_sadl     = output.quantizer;
+  const int shiftInput        = NNFilterHOP::log2OutputScale - log2InputBitdepth;
+  const int shiftOutput       = NNFilterHOP::log2OutputScale - q_output_sadl;
+  assert(shiftInput >= 0);
+  assert(shiftOutput >= 0);
+
+  const int width   = bufDst.Y().width;
+  const int height  = bufDst.Y().height;
+  PelBuf &  bufDstY = bufDst.get(COMPONENT_Y);
+  CPelBuf   bufRecY = pic.getRecBeforeDbfBuf(inferArea).get(COMPONENT_Y);
+
+  for (int c = 0; c < 4; ++c)   // unshuffle on C++ side
+  {
+    for (int y = 0; y < height / 2; ++y)
+    {
+      for (int x = 0; x < width / 2; ++x)
+      {
+        int yy = (y * 2) + c / 2;
+        int xx = (x * 2) + c % 2;
+        if (xx < extLeft || yy < extTop || xx >= width - extRight || yy >= height - extBottom)
+        {
+          continue;
+        }
+        int out;
+        if constexpr (std::is_same<TypeSadlHOP, float>::value)
+        {
+          out = round((output(0, y, x, c) * (1 << shiftOutput) + (float) bufRecY.at(xx, yy) * (1 << shiftInput)));
+        }
+        else
+        {
+          out = ((output(0, y, x, c) << shiftOutput) + (bufRecY.at(xx, yy) << shiftInput));
+        }
+
+        bufDstY.at(xx, yy) = Pel(Clip3<int>(0, (1 << NNFilterHOP::log2OutputScale) - 1, out));
+      }
+    }
+  }
+  
+  PelBuf &bufDstCb = bufDst.get(COMPONENT_Cb);
+  PelBuf &bufDstCr = bufDst.get(COMPONENT_Cr);
+  CPelBuf bufRecCb = pic.getRecoBuf(inferArea).get(COMPONENT_Cb);
+  CPelBuf bufRecCr = pic.getRecoBuf(inferArea).get(COMPONENT_Cr);
+
+  for (int y = 0; y < height / 2; ++y)
+  {
+    for (int x = 0; x < width / 2; ++x)
+    {
+      if (x < extLeft / 2 || y < extTop / 2 || x >= width / 2 - extRight / 2 || y >= height / 2 - extBottom / 2)
+      {
+        continue;
+      }
 
+      int outCb = (bufRecCb.at(x, y) << shiftInput);
+      int outCr = (bufRecCr.at(x, y) << shiftInput);
+
+      bufDstCb.at(x, y) = Pel(Clip3<int>(0, (1 << NNFilterHOP::log2OutputScale) - 1, outCb));
+      bufDstCr.at(x, y) = Pel(Clip3<int>(0, (1 << NNFilterHOP::log2OutputScale) - 1, outCr));
+    }
+  }
+}
+#endif
 void NNFilterHOP::filterBlock(Picture &pic, UnitArea inferArea, int extLeft, int extRight, int extTop, int extBottom,
                               int prmId)
 {
@@ -271,7 +399,37 @@ void NNFilterHOP::filterBlock(Picture &pic, UnitArea inferArea, int extLeft, int
 
   extractOutputs(pic, model, bufDst, inferArea, extLeft, extRight, extTop, extBottom);
 }
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+void NNFilterHOP::filterBlockTemporal(Picture &pic, UnitArea inferArea, int extLeft, int extRight, int extTop, int extBottom,
+                              int prmId)
+{
+  // get model
+  auto &model    = *m_modelTemporal;
+  bool  inter    = pic.slices[0]->getSliceType() != I_SLICE ? true : false;
+  int   qpOffset = (inter ? prmId * 5 : prmId * 2) * (pic.slices[0]->getTLayer() >= 4 ? 1 : -1);
+  int   seqQp    = pic.slices[0]->getPPS()->getPicInitQPMinus26() + 26 + qpOffset;
+  resizeInputsTemporal(inferArea.Y().width, inferArea.Y().height);
 
+  const int    log2InputBitdepth = pic.cs->slice->clpRng(COMPONENT_Y).bd;   // internal bitdepth
+  const double inputScalePred    = (1 << log2InputBitdepth);
+  const double inputScaleQp      = (1 << log2InputQpScale);
+
+  std::vector<InputData> listInputData;
+  listInputData.push_back({ NN_INPUT_REC, 0, inputScalePred, m_input_quantizer_temporal - log2InputBitdepth, true, false });
+  listInputData.push_back({ NN_INPUT_PRED, 1, inputScalePred, m_input_quantizer_temporal - log2InputBitdepth, true, false });
+  listInputData.push_back({ NN_INPUT_REF_LIST_0, 2, inputScalePred, m_input_quantizer_temporal - log2InputBitdepth, true, false });
+  listInputData.push_back({ NN_INPUT_REF_LIST_1, 3, inputScalePred, m_input_quantizer_temporal - log2InputBitdepth, true, false });
+  listInputData.push_back({ NN_INPUT_GLOBAL_QP, 4, inputScaleQp, m_input_quantizer_temporal - log2InputQpScale, true, false });
+
+  NNInference::prepareInputs<TypeSadlHOP>(&pic, inferArea, m_inputsTemporal, seqQp, -1 /* localQp */, -1 /* sliceType */, listInputData);
+
+  NNInference::infer<TypeSadlHOP>(model, m_inputsTemporal);
+
+  PelUnitBuf bufDst = m_scaled[0][prmId].getBuf(inferArea);
+
+  extractOutputsTemporal(pic, model, bufDst, inferArea, extLeft, extRight, extTop, extBottom);
+}
+#endif
 void NNFilterHOP::filter(Picture &pic, const bool isDec)
 {
   const CodingStructure &cs  = *pic.cs;
@@ -307,8 +465,12 @@ void NNFilterHOP::filter(Picture &pic, const bool isDec)
       int            extWidth  = width + extLeft + extRight;
       int            extHeight = height + extTop + extBottom;
       const UnitArea inferArea(cs.area.chromaFormat, Area(extXPos, extYPos, extWidth, extHeight));
-
-      filterBlock(pic, inferArea, extLeft, extRight, extTop, extBottom, prmId);
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+      if (m_picprm->temporal)
+        filterBlockTemporal(pic, inferArea, extLeft, extRight, extTop, extBottom, prmId);
+      else
+#endif
+        filterBlock(pic, inferArea, extLeft, extRight, extTop, extBottom, prmId);
 
       const UnitArea inferAreaNoExt(cs.area.chromaFormat, Area(xPos, yPos, width, height));
       PelUnitBuf     filteredBuf = getFilteredBuf(prmId, inferAreaNoExt);
diff --git a/source/Lib/CommonLib/NNFilterHOP.h b/source/Lib/CommonLib/NNFilterHOP.h
index d5e474838f69525cc809f9317891105800b7efb7..ea48a0df1e48e646cec08e6ae3cbfe5d048c2ea5 100644
--- a/source/Lib/CommonLib/NNFilterHOP.h
+++ b/source/Lib/CommonLib/NNFilterHOP.h
@@ -54,6 +54,9 @@ public:
   static constexpr float nnResidueScaleDerivationUpBound = 1.25f;
   static constexpr float nnResidueScaleDerivationLowBound = 0.0625f;
   static constexpr int max_scale=(1<<log2ResidueScale);
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  static constexpr int minimumTidUseTemporalFiltering = 3;
+#endif
 
   // parameters signaled at slice level
   struct SliceParameters {
@@ -83,6 +86,7 @@ public:
     int              prmNum;
     std::vector<int> prmId; // -1: off
     SliceParameters  sprm;
+    bool             temporal;
   };
 
   static constexpr int scale_candidates[4] = { 0, max_scale, (max_scale + (max_scale << 1)) >> 2, max_scale >> 1 };
@@ -97,6 +101,11 @@ public:
   // just filter the block, output on log2OutputScale bits
   void filterBlock(Picture &pic, UnitArea inferArea, int extLeft, int extRight, int extTop, int extBottom,
                           int prmId);
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  void initTemporal(const std::string &filename);
+  void filterBlockTemporal(Picture &pic, UnitArea inferArea, int extLeft, int extRight, int extTop, int extBottom,
+                          int prmId);
+#endif
 
   // put scaled res+rec in tgt from the filtered src
   void scaleResidualBlock(Picture &pic, ComponentID compID, UnitArea inferArea, CPelBuf src, PelBuf tgt,
@@ -125,6 +134,13 @@ private:
   void resizeInputs(int width, int height);
   std::unique_ptr<sadl::Model<TypeSadlHOP>> m_model;
   std::vector<sadl::Tensor<TypeSadlHOP>>    m_inputs;
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  int  m_blocksizeTemporal[2];   // current inputs size of temporal model
+  int  m_input_quantizer_temporal = 0;
+  void resizeInputsTemporal(int width, int height);
+  std::unique_ptr<sadl::Model<TypeSadlHOP>> m_modelTemporal;
+  std::vector<sadl::Tensor<TypeSadlHOP>>    m_inputsTemporal;
+#endif
   std::vector<PelStorage>                m_filtered; // filtered results of each parameter
   std::vector<PelStorage>                m_scaled[4]; // residue scaling results
   FilterParameters                       *m_picprm; // filtering parameters
diff --git a/source/Lib/CommonLib/NNInference.h b/source/Lib/CommonLib/NNInference.h
index c8e4448167d5bf45cdce4c5f4f90e3e5ac5c5bd2..9b475a42387c53b4ba024f1db30b53cb887b3e45 100644
--- a/source/Lib/CommonLib/NNInference.h
+++ b/source/Lib/CommonLib/NNInference.h
@@ -452,14 +452,12 @@ public:
       case NN_INPUT_SLICE_TYPE:
         fillInputFromConstant<T>(pic, inferArea, inputs[inputData.index], sliceType, inputData.luma, inputData.scale, inputData.shift);
         break;
-#if JVET_AC0177_MULTI_FRAME
       case NN_INPUT_REF_LIST_0:
         fillInputFromBuf<T>(pic, inferArea, inputs[inputData.index], pic->slices[0]->getRefPic(REF_PIC_LIST_0, 0)->getRecoBuf(inferArea), inputData.luma, inputData.chroma, inputData.scale, inputData.shift);
         break;
       case NN_INPUT_REF_LIST_1:
         fillInputFromBuf<T>(pic, inferArea, inputs[inputData.index], pic->slices[0]->getRefPic(REF_PIC_LIST_1, 0)->getRecoBuf(inferArea), inputData.luma, inputData.chroma, inputData.scale, inputData.shift);
         break;
-#endif
 #if JVET_AC0089_COMBINE_INTRA_INTER
       case NN_INPUT_IPB:
         fillInputFromBufIpb<T>(pic, inferArea, inputs[inputData.index], pic->getBlockPredModeBuf(inferArea), inputData.luma, inputData.chroma, inputData.scale, inputData.shift);
@@ -507,14 +505,12 @@ public:
       case NN_INPUT_SLICE_TYPE:
         fillInputFromConstant<T>(pic, inferArea, inputs[inputData.index], sliceType, inputData.luma, inputData.scale, inputData.shift);
         break;
-#if JVET_AC0177_MULTI_FRAME
       case NN_INPUT_REF_LIST_0:
         fillInputFromBuf<T>(pic, inferArea, inputs[inputData.index], pic->slices[0]->getRefPic(REF_PIC_LIST_0, 0)->getRecoBuf(inferArea), inputData.luma, inputData.chroma, inputData.scale, inputData.shift, flip);
         break;
       case NN_INPUT_REF_LIST_1:
         fillInputFromBuf<T>(pic, inferArea, inputs[inputData.index], pic->slices[0]->getRefPic(REF_PIC_LIST_1, 0)->getRecoBuf(inferArea), inputData.luma, inputData.chroma, inputData.scale, inputData.shift, flip);
         break;
-#endif
 #if JVET_AC0089_COMBINE_INTRA_INTER
       case NN_INPUT_IPB:
         fillInputFromBufIpb<T>(pic, inferArea, inputs[inputData.index], pic->getBlockPredModeBuf(inferArea), inputData.luma, inputData.chroma, inputData.scale, inputData.shift, flip);
diff --git a/source/Lib/CommonLib/Picture.h b/source/Lib/CommonLib/Picture.h
index 24dd030fee9fad59fc17faa05bc3a2ba66a0e800..978d9776e3d5062ee57c60d9f25564234062df99 100644
--- a/source/Lib/CommonLib/Picture.h
+++ b/source/Lib/CommonLib/Picture.h
@@ -418,6 +418,9 @@ public:
     m_picprm.nb_blocks_width  = (sps.getMaxPicWidthInLumaSamples() + m_picprm.block_size - 1) / m_picprm.block_size;
     m_picprm.prmId.resize(m_picprm.nb_blocks_height * m_picprm.nb_blocks_width);
     fill(m_picprm.prmId.begin(), m_picprm.prmId.end(), -1);
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+    m_picprm.temporal = sps.getNnlfHopTemporalFilteringEnabledFlag() && slice.getSliceType() != I_SLICE && slice.getTLayer() >= NNFilterHOP::minimumTidUseTemporalFiltering;
+#endif
   }
 #endif
   
diff --git a/source/Lib/CommonLib/Slice.h b/source/Lib/CommonLib/Slice.h
index 841efabccec114eb3b687524ba07e13cc357a4cb..f287e0c5521f9182a95197fff0fd9ef59bb8b950 100644
--- a/source/Lib/CommonLib/Slice.h
+++ b/source/Lib/CommonLib/Slice.h
@@ -1490,6 +1490,9 @@ private:
   uint32_t          m_nnlfHopInferSize[MAX_NUM_NNLF_HOP_INFER_GRANULARITY];
   uint32_t          m_nnlfHopInfSizeExt;
   uint32_t          m_nnlfHopMaxNumPrms;
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  bool              m_nnlfHopTemporalFilteringEnabledFlag;
+#endif
 #endif
 
 #if NN_FILTERING_SET_0
@@ -1783,6 +1786,10 @@ public:
   void                    setNnlfHopInfSizeExt( uint32_t i )                                              { m_nnlfHopInfSizeExt = i;       }
   uint32_t                getNnlfHopMaxNumPrms() const                                                    { return  m_nnlfHopMaxNumPrms;   }
   void                    setNnlfHopMaxNumPrms( uint32_t i )                                              { m_nnlfHopMaxNumPrms = i;       }
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  bool                    getNnlfHopTemporalFilteringEnabledFlag() const                                  { return m_nnlfHopTemporalFilteringEnabledFlag; }
+  void                    setNnlfHopTemporalFilteringEnabledFlag( bool b )                                { m_nnlfHopTemporalFilteringEnabledFlag = b;    }
+#endif
 #endif
 #if NN_FILTERING_SET_0
   bool                    getNnlfSet0EnabledFlag() const                                                  { return m_nnlfSet0EnabledFlag; }
diff --git a/source/Lib/CommonLib/TypeDef.h b/source/Lib/CommonLib/TypeDef.h
index 0328cfb2073b1498fb2c77fb5321e2cea9f09ee1..c68051e8826e22ed368c3ea17b656ef65df9c171 100644
--- a/source/Lib/CommonLib/TypeDef.h
+++ b/source/Lib/CommonLib/TypeDef.h
@@ -54,7 +54,8 @@
 #define NN_HOP_UNIFIED        1
 #if NN_HOP_UNIFIED
 using TypeSadlHOP=float;
-#define NN_HOP_UNIFIED_FORCE_USE 1 // for debug of stage 1
+#define NN_HOP_UNIFIED_FORCE_USE          1 // for debug of stage 1
+#define NN_HOP_UNIFIED_TEMPORAL_FILTERING 1 // hop temporal filtering
 #endif
 
 
@@ -564,11 +565,9 @@ enum NNInputType
   NN_INPUT_SLICE_TYPE = 6,
 #if JVET_AC0089_NNVC_USE_BPM_INFO
   NN_INPUT_IPB = 7,
-#if JVET_AC0177_MULTI_FRAME
-  NN_INPUT_REF_LIST_0 = 8,
-  NN_INPUT_REF_LIST_1 = 9,
-#endif
 #endif
+  NN_INPUT_REF_LIST_0,
+  NN_INPUT_REF_LIST_1,
 #if NN_HOP_UNIFIED_FORCE_USE
   NN_INPUT_ZERO,
 #endif
diff --git a/source/Lib/DecoderLib/DecLib.cpp b/source/Lib/DecoderLib/DecLib.cpp
index 70c6b560d4c750a59c80653333be62677aa81cf4..3dafe96ec819f75f3abd12b19ca1d90719198374 100644
--- a/source/Lib/DecoderLib/DecLib.cpp
+++ b/source/Lib/DecoderLib/DecLib.cpp
@@ -1846,6 +1846,12 @@ void DecLib::xActivateParameterSets( const InputNALUnit nalu )
 
       m_pcPic->initPicprms(*pSlice);
     }
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+    if (sps->getNnlfHopTemporalFilteringEnabledFlag())
+    {
+      m_nnfilterHOP.initTemporal(m_nnlfHopTemporalModelName);
+    }
+#endif
 #endif
   }
   else
diff --git a/source/Lib/DecoderLib/DecLib.h b/source/Lib/DecoderLib/DecLib.h
index 8b28fcc9b26c9ce1f0127abe338ca8474a66ad62..967bf57274332477ebd0aaf1d544be2efd26e060 100644
--- a/source/Lib/DecoderLib/DecLib.h
+++ b/source/Lib/DecoderLib/DecLib.h
@@ -151,6 +151,9 @@ private:
 #if NN_HOP_UNIFIED_FORCE_USE
   int                     m_nnlfHopOption;
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  std::string             m_nnlfHopTemporalModelName;         ///<nnlf temporal hop model
+#endif
 #endif
 #if NN_FILTERING_SET_0
   NNFilterSet0            m_cCNNLF;
@@ -281,6 +284,10 @@ public:
   void               setNnlfHopOption( int i )                              { m_nnlfHopOption = i;          }
   int                getNnlfHopOption() const                               { return m_nnlfHopOption;       }
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  void               setNnlfHopTemporalModelName(std::string s)   { m_nnlfHopTemporalModelName = std::move(s); }
+  const std::string &getNnlfHopTemporalModelName()                { return m_nnlfHopTemporalModelName;         }
+#endif
 #endif
 #if NN_FILTERING_SET_1
   std::string      getNnlfSet1InterLumaModelName()                { return m_nnlfSet1InterLumaModelName;   }
diff --git a/source/Lib/DecoderLib/VLCReader.cpp b/source/Lib/DecoderLib/VLCReader.cpp
index 91fea3b161ff5f1f67a5b6b18cde8991825b5f30..cef74644a8903f722c73c74f805ee26029143737 100644
--- a/source/Lib/DecoderLib/VLCReader.cpp
+++ b/source/Lib/DecoderLib/VLCReader.cpp
@@ -1752,6 +1752,9 @@ void HLSyntaxReader::parseSPS(SPS* pcSPS)
     pcSPS->setNnlfHopInferSize(nnlfHopInferSize);
     READ_UVLC( uiCode, "sps_nnlf_hop_inf_size_ext" );                  pcSPS->setNnlfHopInfSizeExt ( uiCode );
     READ_UVLC( uiCode, "sps_nnlf_hop_max_num_prms" );                  pcSPS->setNnlfHopMaxNumPrms (uiCode );
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+    READ_FLAG( uiCode, "sps_nnlf_hop_temporal_filtering_enabled_flag" ); pcSPS->setNnlfHopTemporalFilteringEnabledFlag ( uiCode ? true : false );
+#endif
   }
 #endif
 
diff --git a/source/Lib/EncoderLib/EncCfg.h b/source/Lib/EncoderLib/EncCfg.h
index 94e017c20faeff18ef50f88cce58f44b81a7db2c..34ec51527974f8fa4b44534cefa12989226e3b82 100644
--- a/source/Lib/EncoderLib/EncCfg.h
+++ b/source/Lib/EncoderLib/EncCfg.h
@@ -818,6 +818,10 @@ protected:
 #if NN_HOP_UNIFIED_FORCE_USE
   int         m_nnlfHopOption;
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  bool        m_nnlfHopTemporalFiltering;
+  std::string m_nnlfHopTemporalModelName;                     ///<nnlf temporal hop model
+#endif
 #endif
 
 #if NN_FILTERING_SET_0
@@ -2191,6 +2195,12 @@ public:
   void         setNnlfHopOption( int i )                              { m_nnlfHopOption = i;          }
   int          getNnlfHopOption() const                               { return m_nnlfHopOption;       }
 #endif
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  void         setUseNnlfHopTemporalFiltering( bool b )               { m_nnlfHopTemporalFiltering = b;            }
+  bool         getUseNnlfHopTemporalFiltering()                 const { return m_nnlfHopTemporalFiltering;         }
+  void               setNnlfHopTemporalModelName(std::string s)       { m_nnlfHopTemporalModelName = std::move(s); }
+  const std::string &getNnlfHopTemporalModelName()                    { return m_nnlfHopTemporalModelName;         }
+#endif
 #endif
   
 #if NN_FILTERING_SET_0
diff --git a/source/Lib/EncoderLib/EncGOP.cpp b/source/Lib/EncoderLib/EncGOP.cpp
index 0156ed69c298911e4c815ad6364afa00003d4136..6e67b8f425d6e0329ad96fc81d2a0a868ceb0ac8 100644
--- a/source/Lib/EncoderLib/EncGOP.cpp
+++ b/source/Lib/EncoderLib/EncGOP.cpp
@@ -257,6 +257,12 @@ void EncGOP::init ( EncLib* pcEncLib )
   {
     m_nnfilterHOP.init(m_pcCfg->getNnlfHopModelName(), m_pcCfg->getSourceWidth(), m_pcCfg->getSourceHeight(), m_pcCfg->getChromaFormatIdc(), m_pcCfg->getNnlfHopMaxNumPrms());
   }
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+  if (m_pcCfg->getUseNnlfHopTemporalFiltering())
+  {
+    m_nnfilterHOP.initTemporal(m_pcCfg->getNnlfHopTemporalModelName());
+  }
+#endif
 #endif
 
 #if WCG_EXT
diff --git a/source/Lib/EncoderLib/EncLib.cpp b/source/Lib/EncoderLib/EncLib.cpp
index 68fed853d2aa376a9c5bee29ed84c77292f0e506..cd2de9f70b7eee0d2f9db0a1237c027ad15ec82a 100644
--- a/source/Lib/EncoderLib/EncLib.cpp
+++ b/source/Lib/EncoderLib/EncLib.cpp
@@ -1582,6 +1582,9 @@ void EncLib::xInitSPS( SPS& sps )
     sps.setNnlfHopInferSize(nnlfHopInferSize);
     sps.setNnlfHopInfSizeExt(m_nnlfHopInfSizeExt);
     sps.setNnlfHopMaxNumPrms(m_intraPeriod == 1 ? 1 : m_nnlfHopMaxNumPrms); // only on/off for all-intra
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+    sps.setNnlfHopTemporalFilteringEnabledFlag(m_nnlfHopTemporalFiltering);
+#endif
   }
 #endif
 
diff --git a/source/Lib/EncoderLib/VLCWriter.cpp b/source/Lib/EncoderLib/VLCWriter.cpp
index f8a9d54bde6897de152a5c13a53d0f4dd95cfcc2..9dc8dbfce6d8a9e543b91674b5c9fc97381e5bee 100644
--- a/source/Lib/EncoderLib/VLCWriter.cpp
+++ b/source/Lib/EncoderLib/VLCWriter.cpp
@@ -1022,6 +1022,9 @@ void HLSWriter::codeSPS( const SPS* pcSPS )
     WRITE_UVLC( pcSPS->getNnlfHopInferSize(NNLF_HOP_INFER_GRANULARITY_BASE),            "sps_nnlf_hop_infer_size_base");
     WRITE_UVLC( pcSPS->getNnlfHopInfSizeExt(),                                          "sps_nnlf_hop_inf_size_ext" );
     WRITE_UVLC( pcSPS->getNnlfHopMaxNumPrms(),                                          "sps_nnlf_hop_max_num_prms" );
+#if NN_HOP_UNIFIED_TEMPORAL_FILTERING
+    WRITE_FLAG( pcSPS->getNnlfHopTemporalFilteringEnabledFlag(),                        "sps_nnlf_hop_temporal_filtering_enabled_flag" );
+#endif
   }
 #endif