diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
index 5b17bac1c5a7841c964564aa8d0825bca102606a..d5d46432f2498e93e112c47afedcc3e7f6df458f 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c
@@ -419,14 +419,14 @@ void nr_simple_dlsch_preprocessor(module_id_t module_id,
   }
 
   uint16_t *vrb_map = RC.nrmac[module_id]->common_channels[CC_id].vrb_map;
-  // for now HARQ PID is fixed and should be the same as in post-processor
-  const int current_harq_pid = slot % 8;
-  NR_UE_harq_t *harq = &sched_ctrl->harq_processes[current_harq_pid];
-  NR_UE_ret_info_t *retInfo = &sched_ctrl->retInfo[current_harq_pid];
+  /* get the PID of a HARQ process awaiting retransmission, or -1 otherwise */
+  sched_ctrl->dl_harq_pid = sched_ctrl->retrans_dl_harq.head;
+  NR_UE_harq_t *harq = &sched_ctrl->harq_processes[sched_ctrl->dl_harq_pid];
   const uint16_t bwpSize = NRRIV2BW(sched_ctrl->active_bwp->bwp_Common->genericParameters.locationAndBandwidth, 275);
   int rbStart = NRRIV2PRBOFFSET(sched_ctrl->active_bwp->bwp_Common->genericParameters.locationAndBandwidth, 275);
 
-  if (harq->round != 0) { /* retransmission */
+  if (sched_ctrl->dl_harq_pid >= 0) { /* retransmission */
+    NR_UE_ret_info_t *retInfo = &sched_ctrl->retInfo[sched_ctrl->dl_harq_pid];
     sched_ctrl->time_domain_allocation = retInfo->time_domain_allocation;
 
     /* ensure that there is a free place for RB allocation */
@@ -564,11 +564,29 @@ void nr_schedule_ue_spec(module_id_t module_id,
                        nrOfLayers)
         >> 3;
 
-    const int current_harq_pid = slot % 8;
+    int8_t current_harq_pid = sched_ctrl->dl_harq_pid;
+    if (current_harq_pid < 0) {
+      /* PP has not selected a specific HARQ Process, get a new one */
+      current_harq_pid = sched_ctrl->available_dl_harq.head;
+      AssertFatal(current_harq_pid >= 0,
+                  "no free HARQ process available for UE %d\n",
+                  UE_id);
+      remove_front_nr_list(&sched_ctrl->available_dl_harq);
+    } else {
+      /* PP selected a specific HARQ process. Check whether it will be a new
+       * transmission or a retransmission, and remove from the corresponding
+       * list */
+      if (sched_ctrl->harq_processes[current_harq_pid].round == 0)
+        remove_nr_list(&sched_ctrl->available_dl_harq, current_harq_pid);
+      else
+        remove_nr_list(&sched_ctrl->retrans_dl_harq, current_harq_pid);
+    }
     NR_UE_harq_t *harq = &sched_ctrl->harq_processes[current_harq_pid];
+    DevAssert(!harq->is_waiting);
+    add_tail_nr_list(&sched_ctrl->feedback_dl_harq, current_harq_pid);
     NR_sched_pucch_t *pucch = &sched_ctrl->sched_pucch[0];
     harq->feedback_slot = pucch->ul_slot;
-    harq->is_waiting = 1;
+    harq->is_waiting = true;
     UE_info->mac_stats[UE_id].dlsch_rounds[harq->round]++;
 
     LOG_D(MAC,
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_phytest.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_phytest.c
index d2d7e30385b7a047ed9e71013d080c028b7cbec2..65a586d3bb449df9681001bdb37c469303a31604 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_phytest.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_phytest.c
@@ -360,6 +360,8 @@ void nr_preprocessor_phytest(module_id_t module_id,
   }
   sched_ctrl->mcs = 9;
   sched_ctrl->numDmrsCdmGrpsNoData = 1;
+  /* get the PID of a HARQ process awaiting retransmission, or -1 otherwise */
+  sched_ctrl->dl_harq_pid = sched_ctrl->retrans_dl_harq.head;
 
   /* mark the corresponding RBs as used */
   for (int rb = 0; rb < sched_ctrl->rbSize; rb++)
diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
index 84489083c5dd4987eb27bf34142a5015042c980b..025f59487dd23b397158d5b2e3396acad4bd5cf3 100644
--- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
+++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c
@@ -291,6 +291,31 @@ void nr_csi_meas_reporting(int Mod_idP,
   }
 }
 
+inline void handle_dl_harq(module_id_t mod_id,
+                           int UE_id,
+                           int8_t harq_pid,
+                           bool success)
+{
+  NR_UE_info_t *UE_info = &RC.nrmac[mod_id]->UE_info;
+  NR_UE_harq_t *harq = &UE_info->UE_sched_ctrl[UE_id].harq_processes[harq_pid];
+  harq->feedback_slot = -1;
+  harq->is_waiting = false;
+  if (success) {
+    add_tail_nr_list(&UE_info->UE_sched_ctrl[UE_id].available_dl_harq, harq_pid);
+    harq->round = 0;
+    harq->ndi ^= 1;
+  } else if (harq->round == MAX_HARQ_ROUNDS) {
+    add_tail_nr_list(&UE_info->UE_sched_ctrl[UE_id].available_dl_harq, harq_pid);
+    harq->round = 0;
+    harq->ndi ^= 1;
+    NR_mac_stats_t *stats = &UE_info->mac_stats[UE_id];
+    stats->dlsch_errors++;
+    LOG_D(MAC, "retransmission error for UE %d (total %d)\n", UE_id, stats->dlsch_errors);
+  } else {
+    add_tail_nr_list(&UE_info->UE_sched_ctrl[UE_id].retrans_dl_harq, harq_pid);
+    harq->round++;
+  }
+}
 
 void handle_nr_uci_pucch_0_1(module_id_t mod_id,
                              frame_t frame,
@@ -310,50 +335,23 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id,
                                 uci_01->ul_cqi,
                                 30);
 
-  // TODO
-  int max_harq_rounds = 4; // TODO define macro
+  NR_ServingCellConfigCommon_t *scc = RC.nrmac[mod_id]->common_channels->ServingCellConfigCommon;
+  const int num_slots = nr_slots_per_frame[*scc->ssbSubcarrierSpacing];
   if (((uci_01->pduBitmap >> 1) & 0x01)) {
-    // handle harq
-    int harq_idx_s = 0;
-
     // iterate over received harq bits
     for (int harq_bit = 0; harq_bit < uci_01->harq->num_harq; harq_bit++) {
-      // search for the right harq process
-      for (int harq_idx = harq_idx_s; harq_idx < NR_MAX_NB_HARQ_PROCESSES; harq_idx++) {
-        // if the gNB received ack with a good confidence
-        if ((slot - 1) == sched_ctrl->harq_processes[harq_idx].feedback_slot) {
-          sched_ctrl->harq_processes[harq_idx].feedback_slot = -1;
-          if ((uci_01->harq->harq_list[harq_bit].harq_value == 1) &&
-              (uci_01->harq->harq_confidence_level == 0)) {
-            // toggle NDI and reset round
-            sched_ctrl->harq_processes[harq_idx].ndi ^= 1;
-            sched_ctrl->harq_processes[harq_idx].round = 0;
-          }
-          else
-            sched_ctrl->harq_processes[harq_idx].round++;
-          sched_ctrl->harq_processes[harq_idx].is_waiting = 0;
-          harq_idx_s = harq_idx + 1;
-          // if the max harq rounds was reached
-          if (sched_ctrl->harq_processes[harq_idx].round == max_harq_rounds) {
-            sched_ctrl->harq_processes[harq_idx].ndi ^= 1;
-            sched_ctrl->harq_processes[harq_idx].round = 0;
-            UE_info->mac_stats[UE_id].dlsch_errors++;
-          }
-          break;
-        }
-        // if feedback slot processing is aborted
-        else if (sched_ctrl->harq_processes[harq_idx].feedback_slot != -1
-                 && (slot - 1) > sched_ctrl->harq_processes[harq_idx].feedback_slot
-                 && sched_ctrl->harq_processes[harq_idx].is_waiting) {
-          sched_ctrl->harq_processes[harq_idx].feedback_slot = -1;
-          sched_ctrl->harq_processes[harq_idx].round++;
-          if (sched_ctrl->harq_processes[harq_idx].round == max_harq_rounds) {
-            sched_ctrl->harq_processes[harq_idx].ndi ^= 1;
-            sched_ctrl->harq_processes[harq_idx].round = 0;
-          }
-          sched_ctrl->harq_processes[harq_idx].is_waiting = 0;
-        }
-      }
+      const uint8_t harq_value = uci_01->harq->harq_list[harq_bit].harq_value;
+      const uint8_t harq_confidence = uci_01->harq->harq_confidence_level;
+      const int8_t pid = sched_ctrl->feedback_dl_harq.head;
+      DevAssert(pid >= 0);
+      remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
+      NR_UE_harq_t *harq = &sched_ctrl->harq_processes[pid];
+      const int feedback_slot = (slot - 1 + num_slots) % num_slots;
+      AssertFatal(harq->feedback_slot == feedback_slot,
+                  "expected feedback slot %d, but found %d instead\n",
+                  harq->feedback_slot, feedback_slot);
+      DevAssert(harq->is_waiting);
+      handle_dl_harq(mod_id, UE_id, pid, harq_value == 1 && harq_confidence == 0);
     }
   }
 }
@@ -376,50 +374,21 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id,
                                 uci_234->ul_cqi,
                                 30);
 
-  // TODO
-  int max_harq_rounds = 4; // TODO define macro
+  NR_ServingCellConfigCommon_t *scc = RC.nrmac[mod_id]->common_channels->ServingCellConfigCommon;
+  const int num_slots = nr_slots_per_frame[*scc->ssbSubcarrierSpacing];
   if ((uci_234->pduBitmap >> 1) & 0x01) {
-    int harq_idx_s = 0;
-    int acknack;
-
     // iterate over received harq bits
     for (int harq_bit = 0; harq_bit < uci_234->harq.harq_bit_len; harq_bit++) {
-      acknack = ((uci_234->harq.harq_payload[harq_bit>>3])>>harq_bit)&0x01;
-      for (int harq_idx = harq_idx_s; harq_idx < NR_MAX_NB_HARQ_PROCESSES-1; harq_idx++) {
-        // if the gNB received ack with a good confidence or if the max harq rounds was reached
-        if ((slot - 1) == sched_ctrl->harq_processes[harq_idx].feedback_slot) {
-          // TODO add some confidence level for when there is no CRC
-          sched_ctrl->harq_processes[harq_idx].feedback_slot = -1;
-          if ((uci_234->harq.harq_crc != 1) && acknack) {
-            // toggle NDI and reset round
-            sched_ctrl->harq_processes[harq_idx].ndi ^= 1;
-            sched_ctrl->harq_processes[harq_idx].round = 0;
-          }
-          else
-            sched_ctrl->harq_processes[harq_idx].round++;
-          sched_ctrl->harq_processes[harq_idx].is_waiting = 0;
-          harq_idx_s = harq_idx + 1;
-          // if the max harq rounds was reached
-          if (sched_ctrl->harq_processes[harq_idx].round == max_harq_rounds) {
-            sched_ctrl->harq_processes[harq_idx].ndi ^= 1;
-            sched_ctrl->harq_processes[harq_idx].round = 0;
-            UE_info->mac_stats[UE_id].dlsch_errors++;
-          }
-          break;
-        }
-        // if feedback slot processing is aborted
-        else if (sched_ctrl->harq_processes[harq_idx].feedback_slot != -1
-                 && (slot - 1) > sched_ctrl->harq_processes[harq_idx].feedback_slot
-                 && sched_ctrl->harq_processes[harq_idx].is_waiting) {
-          sched_ctrl->harq_processes[harq_idx].feedback_slot = -1;
-          sched_ctrl->harq_processes[harq_idx].round++;
-          if (sched_ctrl->harq_processes[harq_idx].round == max_harq_rounds) {
-            sched_ctrl->harq_processes[harq_idx].ndi ^= 1;
-            sched_ctrl->harq_processes[harq_idx].round = 0;
-          }
-          sched_ctrl->harq_processes[harq_idx].is_waiting = 0;
-        }
-      }
+      const int acknack = ((uci_234->harq.harq_payload[harq_bit >> 3]) >> harq_bit) & 0x01;
+      const int8_t pid = sched_ctrl->feedback_dl_harq.head;
+      DevAssert(pid >= 0);
+      remove_front_nr_list(&sched_ctrl->feedback_dl_harq);
+      NR_UE_harq_t *harq = &sched_ctrl->harq_processes[pid];
+      const int feedback_slot = (slot - 1 + num_slots) % num_slots;
+      AssertFatal(harq->feedback_slot == feedback_slot,
+                  "expected feedback slot %d, but found %d instead\n",
+                  harq->feedback_slot, feedback_slot);
+      handle_dl_harq(mod_id, UE_id, pid, uci_234->harq.harq_crc != 1 && acknack);
     }
   }
 }
diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
index bdc01691d8f441b5000b8793d3e73f97b1180a98..000263a99314d3cfdcce14a4676159e4bbdc1c91 100644
--- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
+++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h
@@ -73,6 +73,7 @@
 #define MAX_NUM_BWP 2
 #define MAX_NUM_CORESET 2
 #define MAX_NUM_CCE 90
+#define MAX_HARQ_ROUNDS 4
 /*!\brief Maximum number of random access process */
 #define NR_NB_RA_PROC_MAX 4
 #define MAX_NUM_OF_SSB 64
@@ -336,7 +337,7 @@ typedef struct NR_sched_pusch {
 } NR_sched_pusch_t;
 
 typedef struct NR_UE_harq {
-  uint8_t is_waiting;
+  bool is_waiting;
   uint8_t ndi;
   uint8_t round;
   uint16_t feedback_slot;
@@ -423,6 +424,8 @@ typedef struct {
 
   /// Retransmission-related information
   NR_UE_ret_info_t retInfo[NR_MAX_NB_HARQ_PROCESSES];
+  /// DL HARQ PID to use for this UE, or -1 for "any new"
+  int8_t dl_harq_pid;
 
   uint16_t ta_frame;
   int16_t ta_update;