Compare commits

...

2 Commits

Author SHA1 Message Date
Maxime
278e004ef3 Parallelize UL MAC with DL for TDD case
Assisted-By: Claude Code:Opus 4.6
2026-05-03 05:43:52 -04:00
Maxime
efcc6c521c Implement pipelining of MAC with PHY
Assisted-By: Claude Code:claude-opus-4-6
2026-05-03 05:43:52 -04:00
9 changed files with 317 additions and 55 deletions

View File

@@ -29,6 +29,8 @@
#include "PHY/defs_RU.h"
#include "PHY/defs_gNB.h"
#include "PHY/defs_nr_common.h"
#include "common/utils/nr/nr_common.h"
#include "LAYER2/NR_MAC_gNB/mac_proto.h"
#include "PHY/impl_defs_nr.h"
#include "SCHED_NR/phy_frame_config_nr.h"
#include "SCHED_NR/sched_nr.h"
@@ -47,16 +49,42 @@
#define L1STATSSTRLEN 16384
static void rx_func(processingData_L1_t *param);
static void tx_func(processingData_L1tx_t *info)
/// PHY TX processing — runs on L1_tx_thread, pipelined with MAC (L2_tx_thread)
static void phy_tx_func(processingData_phyTx_t *info)
{
PHY_VARS_gNB *gNB = info->gNB;
int frame_tx = info->frame_tx;
int slot_tx = info->slot_tx;
NR_Sched_Rsp_t *sched_response = &info->sched_response;
nfapi_nr_config_request_scf_t *cfg = &gNB->gNB_config;
int tx_slot_type = nr_slot_select(cfg, frame_tx, slot_tx);
if (tx_slot_type == NR_DOWNLINK_SLOT || tx_slot_type == NR_MIXED_SLOT || get_softmodem_params()->continuous_tx
|| IS_SOFTMODEM_RFSIM || cfg->analog_beamforming_ve.analog_bf_vendor_ext.value) {
start_meas(&gNB->phy_proc_tx);
phy_procedures_gNB_TX(gNB, &sched_response->DL_req, &sched_response->TX_req, &sched_response->UL_dci_req, frame_tx, slot_tx);
processingData_RU_t syncMsgRU;
syncMsgRU.frame_tx = frame_tx;
syncMsgRU.slot_tx = slot_tx;
syncMsgRU.ru = gNB->RU_list[0];
syncMsgRU.timestamp_tx = info->timestamp_tx;
LOG_D(PHY, "gNB: %d.%d : calling RU TX function\n", syncMsgRU.frame_tx, syncMsgRU.slot_tx);
ru_tx_func((void *)&syncMsgRU);
stop_meas(&gNB->phy_proc_tx);
}
}
/// MAC scheduling — runs on L2_tx_thread, dispatches PHY TX work to L1_tx_thread
static void mac_sched_func(processingData_L1tx_t *info)
{
int frame_tx = info->frame;
int slot_tx = info->slot;
int frame_rx = info->frame_rx;
int slot_rx = info->slot_rx;
LOG_D(NR_PHY, "%d.%d running tx_func\n", frame_tx, slot_tx);
LOG_D(NR_PHY, "%d.%d running mac_sched_func\n", frame_tx, slot_tx);
PHY_VARS_gNB *gNB = info->gNB;
NR_IF_Module_t *ifi = gNB->if_inst;
nfapi_nr_config_request_scf_t *cfg = &gNB->gNB_config;
T(T_GNB_PHY_DL_TICK, T_INT(gNB->Mod_id), T_INT(frame_tx), T_INT(slot_tx));
@@ -65,22 +93,38 @@ static void tx_func(processingData_L1tx_t *info)
reset_active_ulsch(gNB, frame_rx);
}
clear_slot_beamid(gNB, slot_tx);
// Pull a pre-allocated sched_response buffer from the free-list pool.
// Blocks if all buffers are in-flight (backpressure: late but correct, same as serial).
notifiedFIFO_elt_t *sched_elt = pullNotifiedFIFO(&gNB->sched_free_list);
if (sched_elt == NULL)
return; // shutdown
processingData_phyTx_t *phyTxMsg = NotifiedFifoData(sched_elt);
NR_Sched_Rsp_t *cur_resp = &phyTxMsg->sched_response;
// Drain pending UL indications from L1_rx before running the scheduler,
// so that RACH/UCI/ULSCH/SRS state is up-to-date when scheduling decisions are made.
{
unsigned int r = atomic_load_explicit(&gNB->ul_ind_read, memory_order_relaxed);
unsigned int w = atomic_load_explicit(&gNB->ul_ind_write, memory_order_acquire);
start_meas(&gNB->ul_indication_stats);
while (r < w) {
NR_UL_IND_t *ul_info = &gNB->ul_ind_pool[r % UL_IND_POOL_SIZE];
ifi->NR_UL_indication(ul_info);
r++;
}
stop_meas(&gNB->ul_indication_stats);
atomic_store_explicit(&gNB->ul_ind_read, r, memory_order_release);
}
nfapi_nr_slot_indication_scf_t ind = {.sfn = frame_tx, .slot = slot_tx};
start_meas(&gNB->slot_indication_stats);
// this variable is very big (multiple MB), so we put it into static storage
// to not overflow the stack while still having it in local (function) scope
// also, tx_func() is only executed by one thread, serially
static NR_Sched_Rsp_t sched_response;
ifi->NR_slot_indication(&ind, &sched_response);
ifi->NR_slot_indication(&ind, cur_resp);
stop_meas(&gNB->slot_indication_stats);
info->gNB = gNB;
// Copy UL PDUs to SPSC queues (safe — copies data, no lasting reference to sched_response)
nr_save_ul_tti_req(gNB, &cur_resp->UL_tti_req);
// At this point, MAC scheduler just ran, including scheduling
// PRACH/PUCCH/PUSCH, so trigger RX chain processing
nr_save_ul_tti_req(gNB, &sched_response.UL_tti_req);
// Trigger RX chain processing
LOG_D(NR_PHY, "Trigger RX for %d.%d\n", frame_rx, slot_rx);
notifiedFIFO_elt_t *res = newNotifiedFIFO_elt(sizeof(processingData_L1_t), 0, &gNB->resp_L1, NULL);
processingData_L1_t *syncMsg = NotifiedFifoData(res);
@@ -91,23 +135,15 @@ static void tx_func(processingData_L1tx_t *info)
res->key = slot_rx;
pushNotifiedFIFO(&gNB->resp_L1, res);
int tx_slot_type = nr_slot_select(cfg, frame_tx, slot_tx);
// TODO check for analog_bf_vendor_ext set to 1 is a workaround while no beam API for beam selection is implemented
if (tx_slot_type == NR_DOWNLINK_SLOT || tx_slot_type == NR_MIXED_SLOT || get_softmodem_params()->continuous_tx
|| IS_SOFTMODEM_RFSIM || cfg->analog_beamforming_ve.analog_bf_vendor_ext.value) {
start_meas(&info->gNB->phy_proc_tx);
phy_procedures_gNB_TX(info->gNB, &sched_response.DL_req, &sched_response.TX_req, &sched_response.UL_dci_req, frame_tx,slot_tx);
PHY_VARS_gNB *gNB = info->gNB;
processingData_RU_t syncMsgRU;
syncMsgRU.frame_tx = frame_tx;
syncMsgRU.slot_tx = slot_tx;
syncMsgRU.ru = gNB->RU_list[0];
syncMsgRU.timestamp_tx = info->timestamp_tx;
LOG_D(PHY, "gNB: %d.%d : calling RU TX function\n", syncMsgRU.frame_tx, syncMsgRU.slot_tx);
ru_tx_func((void *)&syncMsgRU);
stop_meas(&info->gNB->phy_proc_tx);
}
// Dispatch PHY TX to L1_tx_thread — runs concurrently with next slot's MAC scheduling.
// The pool element carries the embedded sched_response and cycles back to sched_free_list
// after L1 processing.
clear_slot_beamid(gNB, slot_tx);
phyTxMsg->gNB = gNB;
phyTxMsg->frame_tx = frame_tx;
phyTxMsg->slot_tx = slot_tx;
phyTxMsg->timestamp_tx = info->timestamp_tx;
pushNotifiedFIFO(&gNB->L1_tx_out, sched_elt);
}
void *L1_rx_thread(void *arg)
@@ -126,18 +162,72 @@ void *L1_rx_thread(void *arg)
}
return NULL;
}
// Added for URLLC, requires MAC scheduling to be split from UL indication
/// L1 TX thread: runs PHY TX processing (encoding, modulation, RU TX)
void *L1_tx_thread(void *arg) {
PHY_VARS_gNB *gNB = (PHY_VARS_gNB*)arg;
while (oai_exit == 0) {
notifiedFIFO_elt_t *res = pullNotifiedFIFO(&gNB->L1_tx_out);
if (res == NULL) // stopping condition, happens only when queue is freed
if (res == NULL) // stopping condition, happens only when queue is aborted
break;
processingData_phyTx_t *info = (processingData_phyTx_t *)NotifiedFifoData(res);
start_meas(&gNB->l1_tx_proc);
phy_tx_func(info);
stop_meas(&gNB->l1_tx_proc);
// Recycle pool element back to free-list for MAC to reuse
pushNotifiedFIFO(&gNB->sched_free_list, res);
}
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;
while (oai_exit == 0) {
notifiedFIFO_elt_t *res = pullNotifiedFIFO(&gNB->L2_tx_out);
if (res == NULL)
break;
processingData_L1tx_t *info = (processingData_L1tx_t *)NotifiedFifoData(res);
start_meas(&gNB->l1_tx_proc);
tx_func(info);
stop_meas(&gNB->l1_tx_proc);
mac_sched_func(info);
delNotifiedFIFO_elt(res);
}
return NULL;
@@ -157,16 +247,20 @@ static void rx_func(processingData_L1_t *info)
if (rx_slot_type == NR_UPLINK_SLOT || rx_slot_type == NR_MIXED_SLOT) {
LOG_D(NR_PHY, "%d.%d Starting RX processing\n", frame_rx, slot_rx);
// UE-specific RX processing for subframe n
NR_UL_IND_t UL_INFO = {.frame = frame_rx, .slot = slot_rx, .module_id = gNB->Mod_id, .CC_id = gNB->CC_id};
// Acquire a pool entry for UL indications (ring buffer: L1_rx writes, scheduler drains)
unsigned int w = atomic_load_explicit(&gNB->ul_ind_write, memory_order_relaxed);
unsigned int r = atomic_load_explicit(&gNB->ul_ind_read, memory_order_acquire);
AssertFatal(w - r < UL_IND_POOL_SIZE, "%d.%d UL indication pool full (%u written, %u read)\n", frame_rx, slot_rx, w, r);
NR_UL_IND_t *UL_INFO = &gNB->ul_ind_pool[w % UL_IND_POOL_SIZE];
*UL_INFO = (NR_UL_IND_t){.frame = frame_rx, .slot = slot_rx, .module_id = gNB->Mod_id, .CC_id = gNB->CC_id};
// Do PRACH RU processing
UL_INFO.rach_ind.pdu_list = UL_INFO.prach_pdu_indication_list;
UL_INFO.rach_ind.number_of_pdus = 0;
UL_INFO->rach_ind.pdu_list = UL_INFO->prach_pdu_indication_list;
UL_INFO->rach_ind.number_of_pdus = 0;
// even if processing is late, we might collect all PRACH
// the last PRACH's frame/slot is when all UE's appear to have accessed
prach_item_t p;
while (spsc_q_get(&gNB->prach_l1rx_queue, &p, sizeof(p)))
L1_nr_prach_procedures(gNB, &p, &UL_INFO.rach_ind);
L1_nr_prach_procedures(gNB, &p, &UL_INFO->rach_ind);
//WA: comment rotation in tx/rx
if (gNB->phase_comp) {
@@ -185,12 +279,10 @@ static void rx_func(processingData_L1_t *info)
}
}
}
phy_procedures_gNB_uespec_RX(gNB, frame_rx, slot_rx, &UL_INFO);
phy_procedures_gNB_uespec_RX(gNB, frame_rx, slot_rx, UL_INFO);
// Call the scheduler
start_meas(&gNB->ul_indication_stats);
gNB->if_inst->NR_UL_indication(&UL_INFO);
stop_meas(&gNB->ul_indication_stats);
// Publish filled entry for the scheduler to drain (no NR_UL_indication here)
atomic_store_explicit(&gNB->ul_ind_write, w + 1, memory_order_release);
notifiedFIFO_elt_t *res = newNotifiedFIFO_elt(sizeof(processingData_L1_t), 0, &gNB->L1_rx_out, NULL);
processingData_L1_t *syncMsg = NotifiedFifoData(res);
@@ -317,14 +409,54 @@ void init_gNB_Tpool(int inst)
// L1 RX result FIFO
initNotifiedFIFO(&gNB->resp_L1);
// L1 TX result FIFO
// L1 TX FIFO: L2 (MAC) → L1 (PHY TX)
initNotifiedFIFO(&gNB->L1_tx_out);
// L2 TX FIFO: RU → L2 (MAC scheduling)
initNotifiedFIFO(&gNB->L2_tx_out);
initNotifiedFIFO(&gNB->L1_rx_out);
// create the RX thread responsible for RX processing start event (resp_L1 msg queue), then launch rx_func()
// Pre-allocated pool of NR_Sched_Rsp_t buffers for MAC/PHY pipelining.
// Pool size: TDD = max(slots_per_TDD_period, 4), FDD = 4.
// Backpressure: MAC blocks when pool is exhausted (late but correct).
NR_DL_FRAME_PARMS *fp = &gNB->frame_parms;
int pool_size;
if (fp->frame_type == TDD) {
int slots_per_tdd = fp->slots_per_frame / get_nb_periods_per_frame(gNB->gNB_config.tdd_table.tdd_period.value);
pool_size = slots_per_tdd > 4 ? slots_per_tdd : 4;
} else {
pool_size = 4;
}
gNB->sched_pool_size = pool_size;
gNB->sched_pool = calloc(pool_size, sizeof(notifiedFIFO_elt_t *));
AssertFatal(gNB->sched_pool, "Failed to allocate sched_pool array\n");
initNotifiedFIFO(&gNB->sched_free_list);
for (int i = 0; i < pool_size; i++) {
notifiedFIFO_elt_t *elt = newNotifiedFIFO_elt(sizeof(processingData_phyTx_t), 0, NULL, NULL);
elt->malloced = false; // pool-managed: don't let abortNotifiedFIFO free these
gNB->sched_pool[i] = elt;
pushNotifiedFIFO(&gNB->sched_free_list, elt);
}
LOG_I(PHY, "Allocated %d sched_response pool buffers (%s, %zu bytes each)\n",
pool_size, fp->frame_type == TDD ? "TDD" : "FDD", sizeof(processingData_phyTx_t));
// L1 RX thread: PUSCH/PUCCH/SRS/PRACH processing
threadCreate(&gNB->L1_rx_thread, L1_rx_thread, (void *)gNB, "L1_rx_thread", gNB->L1_rx_thread_core, OAI_PRIORITY_RT_MAX);
// create the TX thread responsible for TX processing start event (L1_tx_out msg queue), then launch tx_func()
// L1 TX thread: PHY TX processing (LDPC encoding, modulation, RU TX)
threadCreate(&gNB->L1_tx_thread, L1_tx_thread, (void *)gNB, "L1_tx_thread", gNB->L1_tx_thread_core, OAI_PRIORITY_RT_MAX);
// L2 TX thread: MAC scheduling, pipelined with L1 TX
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);
@@ -334,9 +466,31 @@ void term_gNB_Tpool(int inst) {
PHY_VARS_gNB *gNB = RC.gNB[inst];
abortNotifiedFIFO(&gNB->resp_L1);
pthread_join(gNB->L1_rx_thread, NULL);
// Abort sched_free_list first to unblock MAC if it's waiting on backpressure
gNB_MAC_INST *mac = RC.nrmac[0];
abortNotifiedFIFO(&gNB->sched_free_list);
abortNotifiedFIFO(&gNB->L2_tx_out);
pthread_join(mac->L2_tx_thread, NULL);
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]);
free(gNB->sched_pool);
abortTpool(&gNB->threadPool);
abortNotifiedFIFO(&gNB->L1_rx_out);

View File

@@ -995,7 +995,7 @@ void *ru_thread(void *param)
} // end if (ru->feprx)
} // end if (slot_type == NR_UPLINK_SLOT || slot_type == NR_MIXED_SLOT) {
notifiedFIFO_elt_t *resTx = newNotifiedFIFO_elt(sizeof(processingData_L1tx_t), 0, &gNB->L1_tx_out, NULL);
notifiedFIFO_elt_t *resTx = newNotifiedFIFO_elt(sizeof(processingData_L1tx_t), 0, &gNB->L2_tx_out, NULL);
resTx->key = proc->tti_tx;
processingData_L1tx_t *syncMsgTx = NotifiedFifoData(resTx);
*syncMsgTx = (processingData_L1tx_t){.gNB = gNB,
@@ -1004,7 +1004,7 @@ void *ru_thread(void *param)
.frame_rx = proc->frame_rx,
.slot_rx = proc->tti_rx,
.timestamp_tx = proc->timestamp_tx};
pushNotifiedFIFO(&gNB->L1_tx_out, resTx);
pushNotifiedFIFO(&gNB->L2_tx_out, resTx);
}
ru_thread_status = 0;

View File

@@ -168,6 +168,12 @@ void phy_init_nr_gNB(PHY_VARS_gNB *gNB)
// PRACH
init_nr_prach(gNB);
// L1→L2 UL indication pool (ring buffer drained by scheduler)
gNB->ul_ind_pool = calloc(UL_IND_POOL_SIZE, sizeof(NR_UL_IND_t));
AssertFatal(gNB->ul_ind_pool, "Failed to allocate UL indication pool\n");
atomic_init(&gNB->ul_ind_write, 0);
atomic_init(&gNB->ul_ind_read, 0);
int N_RB_UL = cfg->carrier_config.ul_grid_size[cfg->ssb_config.scs_common.value].value;
int n_buf = Prx*max_ul_mimo_layers;
@@ -209,6 +215,8 @@ void phy_free_nr_gNB(PHY_VARS_gNB *gNB)
reset_nr_transport(gNB);
reset_nr_prach(gNB);
free(gNB->ul_ind_pool);
gNB->ul_ind_pool = NULL;
destroy_DLSCH_struct(gNB);

View File

@@ -21,6 +21,7 @@
#include "common/utils/threadPool/task_ans.h"
#include "openair1/PHY/defs_RU.h"
#include "common/utils/ds/spsc_q.h"
#include <stdatomic.h>
#define MAX_NUM_RU_PER_gNB 8
#define MAX_PUCCH0_NID 8
@@ -382,6 +383,12 @@ typedef struct PHY_VARS_gNB_s {
spsc_q_t pucch_queue;
spsc_q_t pusch_queue;
spsc_q_t srs_queue;
/// L1→L2 UL indication ring buffer: L1_rx writes, scheduler drains
#define UL_IND_POOL_SIZE 16
NR_UL_IND_t *ul_ind_pool; ///< pool of UL indication buffers
_Atomic unsigned int ul_ind_write; ///< L1_rx increments after filling pool entry
_Atomic unsigned int ul_ind_read; ///< scheduler increments after processing pool entry
NR_gNB_ULSCH_t *ulsch;
NR_gNB_PHY_STATS_t phy_stats[MAX_MOBILES_PER_GNB];
t_nrPolar_params **polarParams;
@@ -478,7 +485,17 @@ typedef struct PHY_VARS_gNB_s {
time_stats_t srs_iq_matrix_stats;
notifiedFIFO_t resp_L1;
notifiedFIFO_t L1_tx_out;
notifiedFIFO_t L1_tx_out; ///< L2 (MAC) → L1 TX (PHY TX) FIFO
notifiedFIFO_t L2_tx_out; ///< RU → L2 (MAC scheduling) FIFO
/// Pool of pre-allocated processingData_phyTx_t buffers.
/// Single-owner handoff pattern — each element is owned by exactly one thread at a time:
/// sched_free_list → MAC (fills it) → L1_tx_out → PHY (reads it) → sched_free_list
/// No concurrent access: MAC gives up the element on push, PHY acquires it on pull.
/// The FIFO mutex provides the memory barrier ensuring PHY sees all MAC writes.
/// Backpressure: MAC blocks on pull if all elements are in-flight (late but correct).
notifiedFIFO_t sched_free_list;
notifiedFIFO_elt_t **sched_pool; ///< Array of pool element pointers (for cleanup at shutdown)
int sched_pool_size;
notifiedFIFO_t L1_rx_out;
tpool_t threadPool;
int num_pusch_symbols_per_thread;
@@ -486,7 +503,7 @@ typedef struct PHY_VARS_gNB_s {
int dmrs_num_antennas_per_thread;
pthread_t L1_rx_thread;
int L1_rx_thread_core;
pthread_t L1_tx_thread;
pthread_t L1_tx_thread; ///< PHY TX processing thread
int L1_tx_thread_core;
void *scopeData;
} PHY_VARS_gNB;
@@ -566,6 +583,16 @@ typedef struct processingData_L1tx {
PHY_VARS_gNB *gNB;
} processingData_L1tx_t;
/// Data passed to the PHY TX thread for pipelined MAC/PHY processing.
/// Each element is pre-allocated in a pool and cycles between sched_free_list and L1_tx_out.
typedef struct processingData_phyTx {
int frame_tx;
int slot_tx;
openair0_timestamp_t timestamp_tx;
PHY_VARS_gNB *gNB;
NR_Sched_Rsp_t sched_response; ///< embedded sched_response (pool-managed, no malloc in steady state)
} processingData_phyTx_t;
typedef struct processingData_L1rx {
int frame_rx;
int slot_rx;

View File

@@ -971,6 +971,8 @@ int main(int argc, char **argv)
int ret = 1;
initNamedTpool(gNBthreads, &gNB->threadPool, true, "gNB-tpool");
initNotifiedFIFO(&gNB->L1_tx_out);
initNotifiedFIFO(&gNB->L2_tx_out);
initNotifiedFIFO(&gNB->sched_free_list);
// Buffers to store internal memory of slot process
int rx_size = (((14 * UE->frame_parms.N_RB_DL * 12 * sizeof(int32_t)) + 15) >> 4) << 4;

View File

@@ -54,6 +54,8 @@
#define MACRLC_PUSCH_RSSI_THRESHOLD "pusch_RSSI_Threshold"
#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"
@@ -123,6 +125,8 @@
{MACRLC_PUCCH_RSSI_THRESHOLD, HLP_MACRLC_PUCCH_RSSI_THRESHOLD, \
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
@@ -169,6 +173,8 @@
{ .s2 = { config_check_intrange, {-1280, 0}} }, /* PUSCH RSSI threshold range */ \
{ .s2 = { config_check_intrange, {-1280, 0}} }, /* PUCCH RSSI threshold range */ \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
{ .s5 = { NULL } }, \
}
/*---------------------------------------------------------------------------------------------------------------------------------------------------------*/

View File

@@ -1711,6 +1711,10 @@ 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;
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 */
@@ -996,6 +1003,25 @@ typedef struct gNB_MAC_INST_s {
pthread_mutex_t sched_lock;
pthread_t L2_tx_thread; ///< MAC DL scheduling thread
/// 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;