Parallelize UL MAC with DL for TDD case

Assisted-By: Claude Code:Opus 4.6
This commit is contained in:
Maxime
2026-05-02 06:45:47 -04:00
parent efcc6c521c
commit 278e004ef3
5 changed files with 127 additions and 4 deletions

View File

@@ -180,6 +180,44 @@ void *L1_tx_thread(void *arg) {
return NULL;
}
/// L2 UL thread: pre-computes UL scheduling for the upcoming TDD period.
/// Runs without sched_lock — safe because during TDD DL-only slots no L1 RX
/// indications modify UE state, and per-slot resources (CCE, VRB, beam) are
/// indexed by slot so no conflict with the DL thread processing a different slot.
void *L2_UL_TDD_thread(void *arg) {
PHY_VARS_gNB *gNB = (PHY_VARS_gNB *)arg;
gNB_MAC_INST *mac = RC.nrmac[0];
const frame_structure_t *fs = &mac->frame_structure;
const int period_len = fs->numb_slots_period;
const int slots_frame = fs->numb_slots_frame;
// Bypass NR_SCHED_ENSURE_LOCKED asserts — this thread runs without sched_lock
nr_sched_lock_bypassed = true;
while (oai_exit == 0) {
// Wait for trigger from DL thread (first DL slot of TDD period)
pthread_mutex_lock(&mac->ul_precomp_mutex);
while (!mac->ul_precomp_start_req && oai_exit == 0)
pthread_cond_wait(&mac->ul_precomp_start_cond, &mac->ul_precomp_mutex);
if (oai_exit) { pthread_mutex_unlock(&mac->ul_precomp_mutex); break; }
mac->ul_precomp_start_req = false;
const int frame = mac->ul_precomp_frame;
const int slot0 = mac->ul_precomp_slot;
pthread_mutex_unlock(&mac->ul_precomp_mutex);
// Pre-compute UL scheduling for all DL slots in this TDD period
for (int i = 0; i < period_len; i++) {
mac->ul_precomp_dci[i].numPdus = 0;
const int f = (frame + (slot0 + i) / slots_frame) % 1024;
const int s = (slot0 + i) % slots_frame;
if (is_dl_slot(s, fs))
nr_schedule_ulsch(0, f, s, &mac->ul_precomp_dci[i]);
atomic_store_explicit(&mac->ul_precomp_slots_done, i + 1, memory_order_release);
}
}
return NULL;
}
/// L2 TX thread: runs MAC scheduling, dispatches PHY TX to L1_tx_thread
void *L2_tx_thread(void *arg) {
PHY_VARS_gNB *gNB = (PHY_VARS_gNB*)arg;
@@ -409,6 +447,17 @@ void init_gNB_Tpool(int inst)
gNB_MAC_INST *mac = RC.nrmac[0];
threadCreate(&mac->L2_tx_thread, L2_tx_thread, (void *)gNB, "L2_tx_thread", mac->L2_tx_thread_core, OAI_PRIORITY_RT_MAX);
// L2 UL thread: TDD UL pre-scheduling on dedicated core
if (fp->frame_type == TDD) {
int period_len = fp->slots_per_frame / get_nb_periods_per_frame(gNB->gNB_config.tdd_table.tdd_period.value);
mac->ul_precomp_dci = calloc(period_len, sizeof(*mac->ul_precomp_dci));
AssertFatal(mac->ul_precomp_dci, "ul_precomp_dci alloc failed\n");
mac->ul_precomp_period_len = period_len;
pthread_mutex_init(&mac->ul_precomp_mutex, NULL);
pthread_cond_init(&mac->ul_precomp_start_cond, NULL);
threadCreate(&mac->L2_UL_TDD_thread, L2_UL_TDD_thread, (void *)gNB, "L2_UL_TDD_thread", mac->L2_ul_tdd_thread_core, OAI_PRIORITY_RT_MAX);
}
if (!IS_SOFTMODEM_NOSTATS)
threadCreate(&proc->L1_stats_thread, nrL1_stats_thread, (void *)gNB, "L1_stats", -1, OAI_PRIORITY_RT_LOW);
}
@@ -425,6 +474,18 @@ void term_gNB_Tpool(int inst) {
abortNotifiedFIFO(&gNB->L1_tx_out);
pthread_join(gNB->L1_tx_thread, NULL);
// Stop UL pre-scheduling thread (TDD only)
if (gNB->frame_parms.frame_type == TDD) {
pthread_mutex_lock(&mac->ul_precomp_mutex);
mac->ul_precomp_start_req = true; // wake up so it sees oai_exit
pthread_cond_signal(&mac->ul_precomp_start_cond);
pthread_mutex_unlock(&mac->ul_precomp_mutex);
pthread_join(mac->L2_UL_TDD_thread, NULL);
free(mac->ul_precomp_dci);
pthread_mutex_destroy(&mac->ul_precomp_mutex);
pthread_cond_destroy(&mac->ul_precomp_start_cond);
}
// Free pool elements (malloced=false so abort didn't free them)
for (int i = 0; i < gNB->sched_pool_size; i++)
free(gNB->sched_pool[i]);

View File

@@ -55,6 +55,7 @@
#define MACRLC_PUCCH_RSSI_THRESHOLD "pucch_RSSI_Threshold"
#define MACRLC_STATS_MAX_UE "stats_max_ue"
#define MACRLC_L2_TX_THREAD_CORE "L2_tx_thread_core"
#define MACRLC_L2_UL_TDD_THREAD_CORE "L2_ul_tdd_thread_core"
#define HLP_MACRLC_UL_PRBBLACK "SNR threshold to decide whether a PRB will be blacklisted or not"
#define HLP_MACRLC_DL_BLER_UP "Upper threshold of BLER to decrease DL MCS"
@@ -125,6 +126,7 @@
0, .iptr=NULL, .defintval=0, TYPE_INT, 0}, \
{MACRLC_STATS_MAX_UE, HLP_MACRLC_STATS_MAX_UE, 0, .iptr=NULL, .defintval=8, TYPE_INT, 0}, \
{MACRLC_L2_TX_THREAD_CORE, NULL, 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0}, \
{MACRLC_L2_UL_TDD_THREAD_CORE, NULL, 0, .iptr=NULL, .defintval=-1, TYPE_INT, 0}, \
}
// clang-format off
@@ -172,6 +174,7 @@
{ .s2 = { config_check_intrange, {-1280, 0}} }, /* PUCCH RSSI threshold range */ \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
}
/*---------------------------------------------------------------------------------------------------------------------------------------------------------*/

View File

@@ -1712,7 +1712,9 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
RC.nrmac[j]->ulsch_max_frame_inactivity = *gpd(params, np, MACRLC_ULSCH_MAX_FRAME_INACTIVITY)->uptr;
RC.nrmac[j]->stats_max_ue = *gpd(params, np, MACRLC_STATS_MAX_UE)->iptr;
RC.nrmac[j]->L2_tx_thread_core = *gpd(params, np, MACRLC_L2_TX_THREAD_CORE)->iptr;
LOG_I(NR_MAC, "L2_tx_thread_core %d\n", RC.nrmac[j]->L2_tx_thread_core);
RC.nrmac[j]->L2_ul_tdd_thread_core = *gpd(params, np, MACRLC_L2_UL_TDD_THREAD_CORE)->iptr;
LOG_I(NR_MAC, "L2_tx_thread_core %d, L2_ul_tdd_thread_core %d\n",
RC.nrmac[j]->L2_tx_thread_core, RC.nrmac[j]->L2_ul_tdd_thread_core);
RC.nrmac[j]->print_ue_stats = RC.nrmac[j]->stats_max_ue > 0;
NR_bler_options_t *dl_bler_options = &RC.nrmac[j]->dl_bler;
dl_bler_options->upper = *gpd(params, np, MACRLC_DL_BLER_TARGET_UPPER)->dblptr;

View File

@@ -23,6 +23,8 @@
#include <errno.h>
#include <string.h>
__thread bool nr_sched_lock_bypassed = false;
uint8_t nr_get_rv(int rel_round)
{
const uint8_t nr_rv_round_map[4] = {0, 2, 3, 1};
@@ -213,7 +215,40 @@ void gNB_dlsch_ulsch_scheduler(module_id_t module_idP, frame_t frame, slot_t slo
// This schedules the DCI for Uplink and subsequently PUSCH
start_meas(&gNB->schedule_ulsch);
nr_schedule_ulsch(module_idP, frame, slot, &sched_info->UL_dci_req);
if (scc->tdd_UL_DL_ConfigurationCommon && gNB->ul_precomp_dci) {
// TDD with UL pre-scheduling thread active
const frame_structure_t *fs = &gNB->frame_structure;
const int slot_in_period = slot % fs->numb_slots_period;
int done = atomic_load_explicit(&gNB->ul_precomp_slots_done, memory_order_acquire);
if (slot_in_period == 0) {
// Verify previous period completed, then reset and trigger new period
int prev = done;
AssertFatal(prev == 0 || prev >= fs->numb_slots_period,
"%d.%d previous UL pre-computation incomplete (%d/%d)\n",
frame, slot, prev, fs->numb_slots_period);
atomic_store_explicit(&gNB->ul_precomp_slots_done, 0, memory_order_release);
pthread_mutex_lock(&gNB->ul_precomp_mutex);
gNB->ul_precomp_frame = frame;
gNB->ul_precomp_slot = slot;
gNB->ul_precomp_start_req = true;
pthread_cond_signal(&gNB->ul_precomp_start_cond);
pthread_mutex_unlock(&gNB->ul_precomp_mutex);
done = 0; // just reset
}
// At startup the first slot may land mid-period (e.g. ORAN timing sync),
// before the UL thread has ever been triggered. Schedule inline until the
// first period boundary (slot_in_period == 0) is reached.
if (done == 0 && slot_in_period != 0) {
nr_schedule_ulsch(module_idP, frame, slot, &sched_info->UL_dci_req);
} else {
while (atomic_load_explicit(&gNB->ul_precomp_slots_done, memory_order_acquire) <= slot_in_period)
;
if (gNB->ul_precomp_dci[slot_in_period].numPdus > 0)
sched_info->UL_dci_req = gNB->ul_precomp_dci[slot_in_period];
}
} else {
nr_schedule_ulsch(module_idP, frame, slot, &sched_info->UL_dci_req);
}
stop_meas(&gNB->schedule_ulsch);
// This schedules the DCI for Downlink and PDSCH

View File

@@ -16,6 +16,7 @@
#include <string.h>
#include <complex.h>
#include <pthread.h>
#include <stdatomic.h>
#include "fsn.h"
#include "common/utils/ds/seq_arr.h"
#include "common/utils/nr/nr_common.h"
@@ -34,10 +35,16 @@
AssertFatal(rc == 0, "error while locking scheduler mutex, pthread_mutex_unlock() returned %d\n", rc); \
} while (0)
/// Set to true on the UL pre-computation thread so it can call scheduler
/// functions without holding sched_lock (safe during TDD DL-only slots).
extern __thread bool nr_sched_lock_bypassed;
#define NR_SCHED_ENSURE_LOCKED(lock)\
do {\
int rc = pthread_mutex_trylock(lock); \
AssertFatal(rc == EBUSY, "this function should be called with the scheduler mutex locked, pthread_mutex_trylock() returned %d\n", rc);\
if (!nr_sched_lock_bypassed) {\
int rc = pthread_mutex_trylock(lock); \
AssertFatal(rc == EBUSY, "this function should be called with the scheduler mutex locked, pthread_mutex_trylock() returned %d\n", rc);\
}\
} while (0)
/* Commmon */
@@ -1000,6 +1007,21 @@ typedef struct gNB_MAC_INST_s {
/// Core affinity for L2 TX (MAC scheduling) thread, -1 = floating
int L2_tx_thread_core;
/// TDD UL pre-scheduling: pre-computed UL DCIs per slot in TDD period.
/// Filled by L2_UL_TDD_thread at period start; consumed by L2_tx_thread.
nfapi_nr_ul_dci_request_t *ul_precomp_dci;
int ul_precomp_period_len;
pthread_mutex_t ul_precomp_mutex;
pthread_cond_t ul_precomp_start_cond;
bool ul_precomp_start_req; ///< DL thread sets, UL thread clears
_Atomic int ul_precomp_slots_done; ///< UL thread increments per slot, DL thread reads
int ul_precomp_frame; ///< frame of the period being pre-computed
int ul_precomp_slot; ///< first slot of the period being pre-computed
pthread_t L2_UL_TDD_thread; ///< UL pre-scheduling thread (TDD)
/// Core affinity for L2 UL pre-scheduling thread, -1 = floating
int L2_ul_tdd_thread_core;
dlul_mac_stats_t mac_stats;
uint64_t num_scheduled_prach_rx;