Merge remote-tracking branch 'PC-OC/scfdma-uplink-scheduler-rbsize' into integration_2026_w28

Enforce N_PRB=2^x3^y5^z in SC-FDMA uplink scheduler (#247)

With SC-FDMA, the scheduler in uplink needs to schedule N_PRB=2^x3^y5^z.
Not enforcing it may lead to unwanted UE behavior like retransmissions
and re-establishments.

TS38.211 - 6.3.1.4

Reviewed-by: Francesco Mani <email@francescomani.it>
Reviewed-by: Robert Schmidt <robert.schmidt@openairinterface.org>
This commit is contained in:
Robert Schmidt
2026-07-06 18:28:55 +02:00
5 changed files with 65 additions and 42 deletions

View File

@@ -687,7 +687,7 @@ void RCconfig_nr_prs(void)
for (int l = 0; l < PRS_ParamList.paramarray[j][PRS_MUTING_PATTERN1_LIST].numelt; l++)
{
prs_config->MutingPattern1[l] = PRS_ParamList.paramarray[j][PRS_MUTING_PATTERN1_LIST].uptr[l];
if (k == 0) // print only for 0th resource
if (k == 0) // print only for 0th resource
snprintf(str[5]+strlen(str[5]),sizeof(str[5])-strlen(str[5]),"%d, ",prs_config->MutingPattern1[l]);
}
for (int l = 0; l < PRS_ParamList.paramarray[j][PRS_MUTING_PATTERN2_LIST].numelt; l++)
@@ -1733,6 +1733,21 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
else
ul_bler_options->harq_round_max = *gpd(params, np, MACRLC_UL_HARQ_ROUND_MAX)->u8ptr;
RC.nrmac[j]->min_grant_prb = *gpd(params, np, MACRLC_MIN_GRANT_PRB)->u16ptr;
long sc_fdma = NR_PUSCH_Config__transformPrecoder_enabled;
NR_BWP_UplinkCommon_t *bwp = RC.nrmac[j]->common_channels[0].ServingCellConfigCommon->uplinkConfigCommon->initialUplinkBWP;
if (bwp->rach_ConfigCommon->choice.setup->msg3_transformPrecoder != NULL)
sc_fdma = *bwp->rach_ConfigCommon->choice.setup->msg3_transformPrecoder;
int new_min = check_sc_fdma_rbsize(sc_fdma, RC.nrmac[j]->min_grant_prb);
// NR_PUSCH_Config__transformPrecoder_enabled = 0 | NR_PUSCH_Config__transformPrecoder_disabled = 1, so !uses_sc_fdma should
// be used
if (sc_fdma == NR_PUSCH_Config__transformPrecoder_enabled && RC.nrmac[j]->min_grant_prb != new_min) {
LOG_W(NR_MAC,
"min_rb value is set as %d. In SC-FDMA, it should be under format 2^x*3^y*5^z and has been automatically decreased "
"to %d.\n",
RC.nrmac[j]->min_grant_prb,
new_min);
RC.nrmac[j]->min_grant_prb = new_min;
}
RC.nrmac[j]->identity_pm = *gpd(params, np, MACRLC_IDENTITY_PM)->u8ptr;
// PRB Blacklist
uint16_t prbbl[MAX_BWP_SIZE] = {0};
@@ -1819,7 +1834,7 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg)
if (IS_SA_MODE(get_softmodem_params()))
nr_mac_configure_sib1(RC.nrmac[0], &info.plmn, info.nr_cellid, *info.tac);
// read F1 Setup information from config and generated MIB/SIB1
// and store it at MAC for sending later
NR_BCCH_BCH_Message_t *mib = RC.nrmac[0]->common_channels[0].mib;
@@ -2402,9 +2417,9 @@ gNB_RRC_INST *RCconfig_NRRRC()
int rc = itti_create_task(TASK_CU_F1, F1AP_CU_task, &p);
AssertFatal(rc == 0, "Create task for F1AP CU failed\n");
}
// search if in active list
nr_rrc_config_t nrrrc_config = {0};
for (k=0; k <num_gnbs ; k++) {
if (strcmp(GNBSParams[GNB_ACTIVE_GNBS_IDX].strlistptr[k], *(GNBParamList.paramarray[i][GNB_GNB_NAME_IDX].strptr) )== 0) {
@@ -2751,7 +2766,7 @@ int gNB_app_handle_f1ap_gnb_cu_configuration_update(f1ap_gnb_cu_configuration_up
// generate gNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE
msg_ack_p = itti_alloc_new_message (TASK_GNB_APP, 0, F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE);
F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE(msg_ack_p).num_cells_failed_to_be_activated = 0;
F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE(msg_ack_p).have_criticality = 0;
F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE(msg_ack_p).have_criticality = 0;
F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE(msg_ack_p).noofTNLAssociations_to_setup =0;
F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE(msg_ack_p).noofTNLAssociations_failed = 0;
F1AP_GNB_CU_CONFIGURATION_UPDATE_ACKNOWLEDGE(msg_ack_p).noofDedicatedSIDeliveryNeededUEs = 0;
@@ -2774,7 +2789,7 @@ int gNB_app_handle_f1ap_gnb_cu_configuration_update(f1ap_gnb_cu_configuration_up
ngran_node_t get_node_type(void)
{
GET_PARAMS_LIST(GNBParamList, GNBParams, GNBPARAMS_DESC, GNB_CONFIG_STRING_GNB_LIST, NULL);
if (GNBParamList.numelt == 0) // We have no valid configuration, let's return a default
if (GNBParamList.numelt == 0) // We have no valid configuration, let's return a default
return ngran_gNB;
// MAC/RLC params

View File

@@ -423,7 +423,7 @@ static void get_coreset_rb_params(const NR_ControlResourceSet_t *coreset, uint16
AssertFatal(!coreset->ext1 || !coreset->ext1->rb_Offset_r16, "rb-Offset in coreset configuration not handled\n");
*n_rb = 0;
*rb_start = 0;
for (int i = 0; i < 6; i++) {
for (int t = 0; t < 8; t++) {
if ((coreset->frequencyDomainResources.buf[i] >> (7 - t)) & 1) {
@@ -678,18 +678,6 @@ bool update_rb_mcs_tbs(NR_sched_pdsch_t *pdsch, uint32_t num_total_bytes, uint16
return true;
}
static bool multiple_2_3_5(int rb)
{
while (rb % 2 == 0)
rb /= 2;
while (rb % 3 == 0)
rb /= 3;
while (rb % 5 == 0)
rb /= 5;
return (rb == 1);
}
bool nr_find_nb_rb(uint16_t Qm,
uint16_t R,
long transform_precoding,
@@ -702,10 +690,6 @@ bool nr_find_nb_rb(uint16_t Qm,
uint32_t *tbs,
uint16_t *nb_rb)
{
// for transform precoding only RB = 2^a_2 * 3^a_3 * 5^a_5 is allowed with a non-negative
while (transform_precoding == NR_PUSCH_Config__transformPrecoder_enabled && !multiple_2_3_5(nb_rb_max))
nb_rb_max--;
/* is the maximum (not even) enough? */
*nb_rb = nb_rb_max;
*tbs = nr_compute_tbs(Qm, R, *nb_rb, nb_symb_sch, nb_dmrs_prb, 0, 0, nrOfLayers) >> 3;
@@ -727,11 +711,6 @@ bool nr_find_nb_rb(uint16_t Qm,
int hi = nb_rb_max;
int lo = nb_rb_min;
for (int p = (hi + lo) / 2; lo + 1 < hi; p = (hi + lo) / 2) {
// for transform precoding only RB = 2^a_2 * 3^a_3 * 5^a_5 is allowed with a non-negative
while(transform_precoding == NR_PUSCH_Config__transformPrecoder_enabled &&
!multiple_2_3_5(p))
p++;
// If by increasing p for transform precoding we already hit the high, break to avoid infinite loop
if (p == hi)
break;
@@ -2479,11 +2458,11 @@ int get_spf(nfapi_nr_config_request_scf_t *cfg) {
AssertFatal(mu>=0&&mu<4,"Illegal scs %d\n",mu);
return(10 * (1<<mu));
}
}
int to_absslot(nfapi_nr_config_request_scf_t *cfg,int frame,int slot) {
return(get_spf(cfg)*frame) + slot;
return(get_spf(cfg)*frame) + slot;
}
@@ -3768,7 +3747,7 @@ void nr_mac_trigger_release_complete(gNB_MAC_INST *mac, int rnti)
// table. This can happen, e.g., on Msg.3 with C-RNTI, where we create a UE
// MAC context, decode the PDU, find the C-RNTI MAC CE, and then throw the
// newly created context away. See also in _nr_rx_sdu() and commit 93f59a3c6e56f
if (!du_exists_f1_ue_data(rnti))
if (!du_exists_f1_ue_data(rnti))
return;
// unlock the scheduler temporarily to prevent possible deadlocks with

View File

@@ -14,9 +14,33 @@
#include <openair2/UTIL/OPT/opt.h>
#include "LAYER2/nr_rlc/nr_rlc_oai_api.h"
static const uint16_t NR_TRANSFORM_PRECODE_RB_LUT[274] = {
0, 1, 2, 3, 4, 5, 6, 6, 8, 9, 10, 10, 12, 12, 12, 15, 16, 16, 18, 18, 20, 20, 20, 20, 24,
25, 25, 27, 27, 27, 30, 30, 32, 32, 32, 32, 36, 36, 36, 36, 40, 40, 40, 40, 40, 45, 45, 45, 48, 48,
50, 50, 50, 50, 54, 54, 54, 54, 54, 54, 60, 60, 60, 60, 64, 64, 64, 64, 64, 64, 64, 64, 72, 72, 72,
75, 75, 75, 75, 75, 80, 81, 81, 81, 81, 81, 81, 81, 81, 81, 90, 90, 90, 90, 90, 90, 96, 96, 96, 96,
100, 100, 100, 100, 100, 100, 100, 100, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 108, 120, 120, 120, 120, 120,
125, 125, 125, 128, 128, 128, 128, 128, 128, 128, 135, 135, 135, 135, 135, 135, 135, 135, 135, 144, 144, 144, 144, 144, 144,
150, 150, 150, 150, 150, 150, 150, 150, 150, 150, 160, 160, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162, 162,
162, 162, 162, 162, 162, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 180, 192, 192, 192, 192, 192, 192, 192, 192,
200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 200, 216, 216, 216, 216, 216, 216, 216, 216, 216,
225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 225, 240, 240, 240, 243, 243, 243, 243, 243, 243, 243,
250, 250, 250, 250, 250, 250, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 256, 270, 270, 270, 270};
//#define SRS_IND_DEBUG
#define MAX_NUM_DATA_IND 1024
// With SC-FDMA the scheduler in uplink needs to schedule N_PRB=2^x3^y5^z
// Check 6.3.1.4 of 38.211
int check_sc_fdma_rbsize(long transform_precoding, uint16_t rb)
{
DevAssert(rb < 274);
if (transform_precoding == NR_PUSCH_Config__transformPrecoder_enabled) {
return NR_TRANSFORM_PRECODE_RB_LUT[rb];
}
return rb;
}
/* \brief Get the number of UL TDAs that could be used in slot, reachable
* via specific k2. The output parameter first_idx is a pointer to the first
* suitable TDA, and the function returns the number of suitable TDAs, or 0. */
@@ -998,7 +1022,7 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP,
}
}
handle_nr_ul_harq(gNB_mac, UE, current_rnti, harq_pid, sduP == NULL);
} else {
} else {
nr_rx_ra_sdu(gnb_mod_idP, CC_idP, frameP, slotP, current_rnti, sduP, sdu_lenP, harq_pid, timing_advance, ul_cqi, rssi);
}
}
@@ -1760,11 +1784,11 @@ static int apply_ul_retransmission(gNB_MAC_INST *nrmac,
} else {
/* TDA changed vs original retx: recompute DMRS and TB size for new TDA */
NR_pusch_dmrs_t dmrs_info = get_ul_dmrs_params(scc, current_BWP, tda_info, nrOfLayers);
new_sched.rbSize = cand->sched_pusch.rbSize;
new_sched.rbSize = check_sc_fdma_rbsize(current_BWP->transform_precoding, cand->sched_pusch.rbSize);
new_sched.rbStart = cand->sched_pusch.rbStart;
new_sched.tb_size = nr_compute_tbs(retInfo->Qm,
retInfo->R,
cand->sched_pusch.rbSize,
new_sched.rbSize,
tda_info->nrOfSymbols,
dmrs_info.N_PRB_DMRS * dmrs_info.num_dmrs_symb,
0,
@@ -1819,6 +1843,7 @@ static int apply_ul_new_transmission(gNB_MAC_INST *nrmac,
* time_domain_allocation, tda_info already set by pipeline stages).
* Fill remaining dispatch fields: R, Qm, tb_size, dmrs_info, bwp_info. */
NR_sched_pusch_t sched = cand->sched_pusch;
sched.rbSize = check_sc_fdma_rbsize(current_BWP->transform_precoding, sched.rbSize);
sched.frame = sched_frame;
sched.slot = sched_slot;
sched.ul_harq_pid = -1;
@@ -2625,6 +2650,7 @@ bool nr_ul_check_phr(const nr_ul_sched_params_t *params,
pot.dmrs_info = dmrs_info;
pot.nrOfLayers = cand->sched_pusch.nrOfLayers;
pot.rbSize = rbSize;
pot.rbSize = check_sc_fdma_rbsize(current_BWP->transform_precoding, pot.rbSize);
pot.mcs = mcs;
uint16_t R;
@@ -2646,8 +2672,9 @@ bool nr_ul_check_phr(const nr_ul_sched_params_t *params,
NR_sched_pusch_t p1 = pot;
int tp = tx_power;
while (cand->ph < tp && p1.rbSize > params->min_rb) {
p1.rbSize--;
while (cand->ph < tp && p1.rbSize > params->min_rb
&& check_sc_fdma_rbsize(current_BWP->transform_precoding, p1.rbSize - 1) >= params->min_rb) {
p1.rbSize = check_sc_fdma_rbsize(current_BWP->transform_precoding, --p1.rbSize);
p1.tb_size = nr_compute_tbs(p1.Qm, p1.R, p1.rbSize, p1.tda_info.nrOfSymbols, n_dmrs, 0, 0, p1.nrOfLayers) >> 3;
tp = compute_ph_rb_factor(current_BWP->scs, p1.rbSize) + (hasDeltaMCS ? compute_ph_mcs_factor(&p1) : 0);
}

View File

@@ -76,7 +76,7 @@ void schedule_nr_other_sib(module_id_t module_idP,
slot_t slot,
nfapi_nr_dl_tti_request_t *DL_req,
nfapi_nr_tx_data_request_t *TX_req);
struct NR_SchedulingInfo2_r17* find_sib19_sched_info(const struct NR_SI_SchedulingInfo_v1700*);
void schedule_nr_mib(module_id_t module_idP, frame_t frameP, slot_t slotP, nfapi_nr_dl_tti_request_t *DL_req);
@@ -89,6 +89,8 @@ void nr_schedule_ulsch(module_id_t module_id, frame_t frame, slot_t slot, nfapi_
/* \brief default UL preprocessor */
void nr_ulsch_preprocessor(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp_pusch);
int check_sc_fdma_rbsize(long transform_precoding, uint16_t rb);
void nr_mac_pcch_queue_init(NR_COMMON_channels_t *cc);
void nr_mac_pcch_queue_free(NR_COMMON_channels_t *cc);
void nr_mac_pcch_enqueue(module_id_t module_id, uint64_t fiveg_s_tmsi, uint16_t ue_id);
@@ -199,7 +201,7 @@ int nr_acknack_scheduling(gNB_MAC_INST *mac,
int get_pdsch_to_harq_feedback(NR_PUCCH_Config_t *pucch_Config,
nr_dci_format_t dci_format,
uint8_t *pdsch_to_harq_feedback);
int nr_get_pucch_resource(NR_ControlResourceSet_t *coreset,
NR_PUCCH_Config_t *pucch_Config,
int CCEIndex);

View File

@@ -259,7 +259,7 @@ void mac_top_init_gNB(ngran_node_t node_type,
if (RC.nb_nr_macrlc_inst > 0) {
RC.nrmac = (gNB_MAC_INST **) malloc16(RC.nb_nr_macrlc_inst *sizeof(gNB_MAC_INST *));
AssertFatal(RC.nrmac != NULL,"can't ALLOCATE %zu Bytes for %d gNB_MAC_INST with size %zu \n",
RC.nb_nr_macrlc_inst * sizeof(gNB_MAC_INST *),
RC.nb_nr_macrlc_inst, sizeof(gNB_MAC_INST));
@@ -267,13 +267,13 @@ void mac_top_init_gNB(ngran_node_t node_type,
for (module_id_t i = 0; i < RC.nb_nr_macrlc_inst; i++) {
RC.nrmac[i] = (gNB_MAC_INST *) malloc16(sizeof(gNB_MAC_INST));
AssertFatal(RC.nrmac != NULL,"can't ALLOCATE %zu Bytes for %d gNB_MAC_INST with size %zu \n",
RC.nb_nr_macrlc_inst * sizeof(gNB_MAC_INST *),
RC.nb_nr_macrlc_inst, sizeof(gNB_MAC_INST));
LOG_D(MAC,"[MAIN] ALLOCATE %zu Bytes for %d gNB_MAC_INST @ %p\n",sizeof(gNB_MAC_INST), RC.nb_nr_macrlc_inst, RC.mac);
bzero(RC.nrmac[i], sizeof(gNB_MAC_INST));
nr_mac_pcch_queue_init(&RC.nrmac[i]->common_channels[0]);