From 7dbb6251b8f67b9d0b2244810920ee2cfad876c4 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 3 Feb 2026 19:29:48 +0100 Subject: [PATCH 01/17] Remove unused nb_nr_mac_CC --- common/ran_context.h | 2 -- openair1/SIMULATION/NR_PHY/dlsim.c | 3 --- openair1/SIMULATION/NR_PHY/ulsim.c | 3 --- openair2/GNB_APP/gnb_config.c | 2 -- 4 files changed, 10 deletions(-) diff --git a/common/ran_context.h b/common/ran_context.h index 91baa78d84..f6784c733a 100644 --- a/common/ran_context.h +++ b/common/ran_context.h @@ -69,8 +69,6 @@ typedef struct { int nb_nr_macrlc_inst; /// Number of component carriers per instance in this node int *nb_mac_CC; - /// Number of component carriers per instance in this node - int *nb_nr_mac_CC; /// Number of L1 instances in this node int nb_L1_inst; /// Number of NB_IoT L1 instances in this node diff --git a/openair1/SIMULATION/NR_PHY/dlsim.c b/openair1/SIMULATION/NR_PHY/dlsim.c index df97840e24..b83d11db98 100644 --- a/openair1/SIMULATION/NR_PHY/dlsim.c +++ b/openair1/SIMULATION/NR_PHY/dlsim.c @@ -799,9 +799,6 @@ int main(int argc, char **argv) }; RC.nb_nr_macrlc_inst = 1; - RC.nb_nr_mac_CC = (int*)malloc(RC.nb_nr_macrlc_inst*sizeof(int)); - for (i = 0; i < RC.nb_nr_macrlc_inst; i++) - RC.nb_nr_mac_CC[i] = 1; mac_top_init_gNB(ngran_gNB, scc, &conf, &rlc_config); gNB_mac = RC.nrmac[0]; nr_mac_config_scc(RC.nrmac[0], scc, &conf); diff --git a/openair1/SIMULATION/NR_PHY/ulsim.c b/openair1/SIMULATION/NR_PHY/ulsim.c index fa8643023f..3e6cbac8b6 100644 --- a/openair1/SIMULATION/NR_PHY/ulsim.c +++ b/openair1/SIMULATION/NR_PHY/ulsim.c @@ -832,9 +832,6 @@ int main(int argc, char *argv[]) }; RC.nb_nr_macrlc_inst = 1; - RC.nb_nr_mac_CC = (int*)malloc(RC.nb_nr_macrlc_inst*sizeof(int)); - for (i = 0; i < RC.nb_nr_macrlc_inst; i++) - RC.nb_nr_mac_CC[i] = 1; mac_top_init_gNB(ngran_gNB, scc, &conf, &rlc_config); nr_mac_config_scc(RC.nrmac[0], scc, &conf); diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index d3618bdaaf..0fab7b6dfa 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1498,10 +1498,8 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) ngran_node_t node_type = get_node_type(); mac_top_init_gNB(node_type, scc, &config, &default_rlc_config); - RC.nb_nr_mac_CC = (int *)malloc(RC.nb_nr_macrlc_inst * sizeof(int)); for (j = 0; j < RC.nb_nr_macrlc_inst; j++) { - RC.nb_nr_mac_CC[j] = *(MacRLC_ParamList.paramarray[j][MACRLC_CC_IDX].iptr); RC.nrmac[j]->pusch_target_snrx10 = *(MacRLC_ParamList.paramarray[j][MACRLC_PUSCHTARGETSNRX10_IDX].iptr); RC.nrmac[j]->pusch_rssi_threshold = *(MacRLC_ParamList.paramarray[j][MACRLC_PUSCH_RSSI_THRES_IDX].iptr); RC.nrmac[j]->pucch_rssi_threshold = *(MacRLC_ParamList.paramarray[j][MACRLC_PUCCH_RSSI_THRES_IDX].iptr); From 2ab6b88d64ca2e4c14d40093be993b0cf10db791 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 23 Jan 2026 17:03:08 +0100 Subject: [PATCH 02/17] Remove write-only or unused variables --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 7 +++---- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 5 +---- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 5 ----- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index dbf285e931..2ee068121b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -326,7 +326,7 @@ static uint32_t update_dlsch_buffer(frame_t frame, slot_t slot, NR_UE_info_t *UE { NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; sched_ctrl->num_total_bytes = 0; - sched_ctrl->dl_pdus_total = 0; + int dl_pdus_total = 0; logical_chan_id_t ch[NR_MAX_NUM_LCID] = {0}; int n = 0; @@ -350,7 +350,7 @@ static uint32_t update_dlsch_buffer(frame_t frame, slot_t slot, NR_UE_info_t *UE continue; sched_ctrl->rlc_status[lcid] = ret[i]; - sched_ctrl->dl_pdus_total += sched_ctrl->rlc_status[lcid].pdus_in_buffer; + dl_pdus_total += sched_ctrl->rlc_status[lcid].pdus_in_buffer; sched_ctrl->num_total_bytes += sched_ctrl->rlc_status[lcid].bytes_in_buffer; LOG_D(MAC, "%4d.%2d UE %04x LCID %d status: %d bytes, %d PDUs, total buffer %d bytes %d PDUs\n", @@ -361,7 +361,7 @@ static uint32_t update_dlsch_buffer(frame_t frame, slot_t slot, NR_UE_info_t *UE ret[i].bytes_in_buffer, ret[i].pdus_in_buffer, sched_ctrl->num_total_bytes, - sched_ctrl->dl_pdus_total); + dl_pdus_total); } return sched_ctrl->num_total_bytes; } @@ -914,7 +914,6 @@ static void pf_dl(gNB_MAC_INST *mac, // (for 4 PDUs) and optionally + 2 for TA. Once RLC gives the number of // PDUs, we replace with 3 * numPDUs const int oh = 3 * 4 + (sched_ctrl->ta_apply ? 2 : 0); - //const int oh = 3 * sched_ctrl->dl_pdus_total + (sched_ctrl->ta_apply ? 2 : 0); nr_find_nb_rb(sched_pdsch.Qm, sched_pdsch.R, 1, // no transform precoding for DL diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index 0998eb463b..e67e99d96a 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -568,7 +568,6 @@ static int nr_process_mac_pdu(instance_t module_idP, sched_pusch->dmrs_info.num_dmrs_symb * sched_pusch->dmrs_info.N_PRB_DMRS, // n_dmrs deltaMCS, true); - sched_ctrl->ph0 = PH; /* 38.133 Table10.1.18.1-1 */ sched_ctrl->pcmax = PCMAX - 29; LOG_D(NR_MAC, @@ -856,7 +855,6 @@ static void nr_rx_ra_sdu(const module_id_t mod_id, } if (timing_advance != 0xffff) UE_scheduling_control->ta_update = timing_advance; - UE_scheduling_control->raw_rssi = rssi; LOG_D(NR_MAC, "[UE %04x] PUSCH TPC %d and TA %d\n", UE->rnti, UE_scheduling_control->tpc0, UE_scheduling_control->ta_update); LOG_D(NR_MAC, "[RAPROC] Received %s:\n", ra->ra_type == RA_2_STEP ? "MsgA-PUSCH" : "Msg3"); @@ -987,7 +985,6 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, if (timing_advance != 0xffff) UE_scheduling_control->ta_update = timing_advance; - UE_scheduling_control->raw_rssi = rssi; UE_scheduling_control->pusch_snrx10 = ul_cqi * 5 - 640 - (txpower_calc * 10); if (UE_scheduling_control->tpc0 > 1) LOG_D(NR_MAC, @@ -998,7 +995,7 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, UE_scheduling_control->tpc0, UE_scheduling_control->ta_update, UE_scheduling_control->pusch_snrx10, - UE_scheduling_control->raw_rssi, + rssi, txpower_calc, UE_scheduling_control->ph, UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.mcs, diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index adbba9c772..4de07ad299 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -628,8 +628,6 @@ typedef struct { /// PHR info: power headroom level (dB) int ph; - /// PHR info: power headroom level (dB) for 1 PRB - int ph0; /// PHR info: nominal UE transmit power levels (dBm) int pcmax; @@ -643,7 +641,6 @@ typedef struct { /// total amount of data awaiting for this UE uint32_t num_total_bytes; - uint16_t dl_pdus_total; /// per-LC status data mac_rlc_status_resp_t rlc_status[NR_MAX_NUM_LCID]; @@ -656,10 +653,8 @@ typedef struct { bool ta_apply; uint8_t tpc0; uint8_t tpc1; - int raw_rssi; int pusch_snrx10; int pucch_snrx10; - uint16_t ul_rssi; int pusch_consecutive_dtx_cnt; int pucch_consecutive_dtx_cnt; bool ul_failure; From 15ac13e882c203d37866809ce5d1c56027ecff49 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Thu, 30 Oct 2025 19:31:19 +0100 Subject: [PATCH 03/17] Add assert: on Msg3, we cannot have phr_txpower_calc It is not filled in the Msg3 scheduler. --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index e67e99d96a..c1792cf516 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -850,6 +850,7 @@ static void nr_rx_ra_sdu(const module_id_t mod_id, DevAssert(harq_pid >= 0 && harq_pid < 8); if (ul_cqi != 0xff) { NR_UE_ul_harq_t *harq = &UE_scheduling_control->ul_harq_processes[harq_pid]; + DevAssert(harq->sched_pusch.phr_txpower_calc == 0); UE_scheduling_control->tpc0 = nr_get_tpc(target_snrx10, ul_cqi, 30, harq->sched_pusch.phr_txpower_calc); UE_scheduling_control->pusch_snrx10 = ul_cqi * 5 - 640 - harq->sched_pusch.phr_txpower_calc * 10; } From f282402ea21c9113f57c8808d44604bd356e7d84 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 2 Feb 2026 18:19:14 +0100 Subject: [PATCH 04/17] Move power configuration into struct Group related parameters and simplify setup of these parameters. --- openair2/GNB_APP/gnb_config.c | 33 +++++++++++-------- .../LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c | 8 ++--- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 10 +++--- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 29 ++++++++-------- 4 files changed, 41 insertions(+), 39 deletions(-) diff --git a/openair2/GNB_APP/gnb_config.c b/openair2/GNB_APP/gnb_config.c index 0fab7b6dfa..73747638b1 100644 --- a/openair2/GNB_APP/gnb_config.c +++ b/openair2/GNB_APP/gnb_config.c @@ -1492,28 +1492,33 @@ void RCconfig_nr_macrlc(configmodule_interface_t *cfg) AssertFatal(config.first_active_bwp <= config.num_additional_bwps, "1st active BWP does not belog to the configured BWPs\n"); if (MacRLC_ParamList.numelt > 0) { + AssertFatal(MacRLC_ParamList.numelt == 1, "only one MACRLCs section supported!\n"); + AssertFatal(MacRLC_ParamList.numelt == RC.nb_nr_macrlc_inst, "only one MACRLCs section supported!\n"); /* NR RLC config is needed by mac_top_init_gNB() */ nr_rlc_configuration_t default_rlc_config; config_rlc(cfg, &default_rlc_config); + config.pusch.target_snrx10 = *(MacRLC_ParamList.paramarray[0][MACRLC_PUSCHTARGETSNRX10_IDX].iptr); + config.pusch.rssi_threshold = *(MacRLC_ParamList.paramarray[0][MACRLC_PUSCH_RSSI_THRES_IDX].iptr); + config.pucch.rssi_threshold = *(MacRLC_ParamList.paramarray[0][MACRLC_PUCCH_RSSI_THRES_IDX].iptr); + config.pucch.target_snrx10 = *(MacRLC_ParamList.paramarray[0][MACRLC_PUCCHTARGETSNRX10_IDX].iptr); + config.ul_prbblack_SNR_threshold = *(MacRLC_ParamList.paramarray[0][MACRLC_UL_PRBBLACK_SNR_THRESHOLD_IDX].iptr); + config.pucch.failure_thres = *(MacRLC_ParamList.paramarray[0][MACRLC_PUCCHFAILURETHRES_IDX].iptr); + config.pusch.failure_thres = *(MacRLC_ParamList.paramarray[0][MACRLC_PUSCHFAILURETHRES_IDX].iptr); + + LOG_I(NR_MAC, + "PUSCH Target %d RSSI thresh %d Failure %d, PUCCH Target %d RSSI thresh %d Failure %d\n", + config.pusch.target_snrx10, + config.pusch.rssi_threshold, + config.pusch.failure_thres, + config.pucch.target_snrx10, + config.pucch.rssi_threshold, + config.pucch.failure_thres); + ngran_node_t node_type = get_node_type(); mac_top_init_gNB(node_type, scc, &config, &default_rlc_config); for (j = 0; j < RC.nb_nr_macrlc_inst; j++) { - RC.nrmac[j]->pusch_target_snrx10 = *(MacRLC_ParamList.paramarray[j][MACRLC_PUSCHTARGETSNRX10_IDX].iptr); - RC.nrmac[j]->pusch_rssi_threshold = *(MacRLC_ParamList.paramarray[j][MACRLC_PUSCH_RSSI_THRES_IDX].iptr); - RC.nrmac[j]->pucch_rssi_threshold = *(MacRLC_ParamList.paramarray[j][MACRLC_PUCCH_RSSI_THRES_IDX].iptr); - RC.nrmac[j]->pucch_target_snrx10 = *(MacRLC_ParamList.paramarray[j][MACRLC_PUCCHTARGETSNRX10_IDX].iptr); - RC.nrmac[j]->ul_prbblack_SNR_threshold = *(MacRLC_ParamList.paramarray[j][MACRLC_UL_PRBBLACK_SNR_THRESHOLD_IDX].iptr); - RC.nrmac[j]->pucch_failure_thres = *(MacRLC_ParamList.paramarray[j][MACRLC_PUCCHFAILURETHRES_IDX].iptr); - RC.nrmac[j]->pusch_failure_thres = *(MacRLC_ParamList.paramarray[j][MACRLC_PUSCHFAILURETHRES_IDX].iptr); - - LOG_I(NR_MAC, - "PUSCH Target %d, PUCCH Target %d, PUCCH Failure %d, PUSCH Failure %d\n", - RC.nrmac[j]->pusch_target_snrx10, - RC.nrmac[j]->pucch_target_snrx10, - RC.nrmac[j]->pucch_failure_thres, - RC.nrmac[j]->pusch_failure_thres); if (strcmp(*(MacRLC_ParamList.paramarray[j][MACRLC_TRANSPORT_N_PREFERENCE_IDX].strptr), "local_RRC") == 0) { // check number of instances is same as RRC/PDCP diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c index 8366026691..062bdfc221 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c @@ -879,7 +879,7 @@ static NR_UE_harq_t *find_harq(frame_t frame, slot_t slot, NR_UE_info_t * UE, in void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, const nfapi_nr_uci_pucch_pdu_format_0_1_t *uci_01) { gNB_MAC_INST *nrmac = RC.nrmac[mod_id]; - int rssi_threshold = nrmac->pucch_rssi_threshold; + int rssi_threshold = nrmac->radio_config.pucch.rssi_threshold; NR_SCHED_LOCK(&nrmac->sched_lock); NR_UE_info_t *UE = find_nr_UE(&nrmac->UE_info, uci_01->rnti); bool is_ra = false; @@ -932,7 +932,7 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con // tpc (power control) only if we received AckNack if (uci_01->harq.harq_confidence_level == 0 && uci_01->ul_cqi != 0xff) { sched_ctrl->pucch_snrx10 = uci_01->ul_cqi * 5 - 640; - sched_ctrl->tpc1 = nr_get_tpc(nrmac->pucch_target_snrx10, uci_01->ul_cqi, 30, 0); + sched_ctrl->tpc1 = nr_get_tpc(nrmac->radio_config.pucch.target_snrx10, uci_01->ul_cqi, 30, 0); } else sched_ctrl->tpc1 = 1; sched_ctrl->tpc1 = nr_limit_tpc(sched_ctrl->tpc1, uci_01->rssi, rssi_threshold); @@ -954,7 +954,7 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c { gNB_MAC_INST *nrmac = RC.nrmac[mod_id]; NR_SCHED_LOCK(&nrmac->sched_lock); - int rssi_threshold = nrmac->pucch_rssi_threshold; + int rssi_threshold = nrmac->radio_config.pucch.rssi_threshold; NR_UE_info_t *UE = find_nr_UE(&nrmac->UE_info, uci_234->rnti); if (!UE) { @@ -969,7 +969,7 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c // TODO PUCCH2 SNR computation is not correct -> ignore the following if (uci_234->ul_cqi != 0xff) { sched_ctrl->pucch_snrx10 = uci_234->ul_cqi * 5 - 640; - sched_ctrl->tpc1 = nr_get_tpc(nrmac->pucch_target_snrx10, uci_234->ul_cqi, 30, 0); + sched_ctrl->tpc1 = nr_get_tpc(nrmac->radio_config.pucch.target_snrx10, uci_234->ul_cqi, 30, 0); sched_ctrl->tpc1 = nr_limit_tpc(sched_ctrl->tpc1, uci_234->rssi, rssi_threshold); } diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index c1792cf516..f0b9e9007b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -795,7 +795,7 @@ static void nr_rx_ra_sdu(const module_id_t mod_id, } } - const int target_snrx10 = mac->pusch_target_snrx10; + const int target_snrx10 = mac->radio_config.pusch.target_snrx10; if (!sdu) { // NACK if (cfra) // no Msg3 on CFRA, no problem return; @@ -944,9 +944,9 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, gNB_MAC_INST *gNB_mac = RC.nrmac[gnb_mod_idP]; const int current_rnti = rntiP; LOG_D(NR_MAC, "rx_sdu for rnti %04x\n", current_rnti); - const int target_snrx10 = gNB_mac->pusch_target_snrx10; - const int rssi_threshold = gNB_mac->pusch_rssi_threshold; - const int pusch_failure_thres = gNB_mac->pusch_failure_thres; + const int target_snrx10 = gNB_mac->radio_config.pusch.target_snrx10; + const int rssi_threshold = gNB_mac->radio_config.pusch.rssi_threshold; + const int pusch_failure_thres = gNB_mac->radio_config.pusch.failure_thres; NR_UE_info_t *UE = find_nr_UE(&gNB_mac->UE_info, current_rnti); if (UE) { NR_UE_sched_ctrl_t *UE_scheduling_control = &UE->UE_sched_ctrl; @@ -1582,7 +1582,7 @@ void handle_nr_srs_measurements(const module_id_t module_id, sprintf(stats->srs_stats, "UL-SNR %i dB", wide_band_snr_dB); - const int ul_prbblack_SNR_threshold = nr_mac->ul_prbblack_SNR_threshold; + const int ul_prbblack_SNR_threshold = nr_mac->radio_config.ul_prbblack_SNR_threshold; uint16_t *ulprbbl = nr_mac->ulprbbl; uint16_t num_rbs = nr_srs_bf_report.prg_size * nr_srs_bf_report.reported_symbol_list[0].num_prgs; diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 4de07ad299..e995cc9da0 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -184,6 +184,15 @@ typedef enum { SSB_SINR, } nr_config_report_type_t; +typedef struct nr_power_config { + /// target SNR + int target_snrx10; + /// RSSI threshold for power control. Limits power control commands when RSSI reaches threshold. + int rssi_threshold; + /// Failure threshold (compared to consecutive PUSCH DTX) + int failure_thres; +} nr_power_config_t; + typedef struct nr_mac_config_s { nr_pdsch_AntennaPorts_t pdsch_AntennaPorts; int pusch_AntennaPorts; @@ -197,8 +206,10 @@ typedef struct nr_mac_config_s { bool use_deltaMCS; int maxMIMO_layers; bool disable_harq; - //int pusch_TargetSNRx10; - //int pucch_TargetSNRx10; + nr_power_config_t pusch; + /// SNR threshold needed to put or not a PRB in the black list + int ul_prbblack_SNR_threshold; + nr_power_config_t pucch; nr_mac_timers_t timer_config; int num_dlharq; int num_ulharq; @@ -901,20 +912,6 @@ typedef struct gNB_MAC_INST_s { /// Pointer to IF module instance for PHY NR_IF_Module_t *if_inst; pthread_t stats_thread; - /// Pusch target SNR - int pusch_target_snrx10; - /// RSSI threshold for power control. Limits power control commands when RSSI reaches threshold. - int pusch_rssi_threshold; - /// Pucch target SNR - int pucch_target_snrx10; - /// RSSI threshold for PUCCH power control. Limits power control commands when RSSI reaches threshold. - int pucch_rssi_threshold; - /// SNR threshold needed to put or not a PRB in the black list - int ul_prbblack_SNR_threshold; - /// PUCCH Failure threshold (compared to consecutive PUCCH DTX) - int pucch_failure_thres; - /// PUSCH Failure threshold (compared to consecutive PUSCH DTX) - int pusch_failure_thres; /// Subcarrier Offset int ssb_SubcarrierOffset; int ssb_OffsetPointA; From 28d770108f87aabf2ffb4edb0de0c30cd3d9e1aa Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 3 Nov 2025 16:32:19 +0100 Subject: [PATCH 05/17] Log PUSCH power control variables via T trace GNB_MAC_PUSCH_POWER_CONTROL Include plotting tool and README on how to log and visualize graphics. --- common/utils/T/T_messages.txt | 4 ++ .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 14 +++++- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 3 ++ tools/plots/README.md | 40 +++++++++++++++ tools/plots/plot-power-control.gp.sh | 49 +++++++++++++++++++ 5 files changed, 109 insertions(+), 1 deletion(-) create mode 100755 tools/plots/plot-power-control.gp.sh diff --git a/common/utils/T/T_messages.txt b/common/utils/T/T_messages.txt index 432ad30262..c763c691a5 100644 --- a/common/utils/T/T_messages.txt +++ b/common/utils/T/T_messages.txt @@ -213,6 +213,10 @@ ID = GNB_MAC_UL_PDU_WITH_DATA DESC = NR MAC uplink PDU for an UE GROUP = ALL:MAC:GNB:WIRESHARK FORMAT = int,gNB_ID : int,CC_id : int,rnti : int,frame : int,slot : int,harq_pid : buffer,data +ID = GNB_MAC_PUSCH_POWER_CONTROL + DESC = NR MAC PUSCH power control-related data + GROUP = ALL:MAC:GNB:CSV + FORMAT = int,rnti : int,frame : int,slot : int,snrx10 : int,phr : int,tpc : int,tb_size : int,txpower_calc : int,rbSize : int,mcs : int,rssi ID = GNB_MAC_DL_RAR_PDU_WITH_DATA DESC = NR MAC downlink PDU for RAR GROUP = ALL:MAC:GNB:WIRESHARK diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index f0b9e9007b..58fee1c8b8 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -987,6 +987,17 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, if (timing_advance != 0xffff) UE_scheduling_control->ta_update = timing_advance; UE_scheduling_control->pusch_snrx10 = ul_cqi * 5 - 640 - (txpower_calc * 10); + const NR_sched_pusch_t *sched_pusch = &UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch; + (void) sched_pusch; // avoids warnings of unused sched_pusch when compiling without T + T(T_GNB_MAC_PUSCH_POWER_CONTROL, T_INT(rntiP), T_INT(frameP), T_INT(slotP), + T_INT(UE_scheduling_control->pusch_snrx10), + T_INT(UE_scheduling_control->ph), + T_INT(sched_pusch->tpc_pusch), + T_INT(sched_pusch->tb_size), + T_INT(txpower_calc), + T_INT(sched_pusch->rbSize), + T_INT(sched_pusch->mcs), + T_INT(rssi)); if (UE_scheduling_control->tpc0 > 1) LOG_D(NR_MAC, "[UE %04x] %d.%d. PUSCH TPC %d and TA %d pusch_snrx10 %d rssi %d phrx_tx_power %d PHR (1PRB) %d mcs %d, nb_rb %d\n", @@ -2607,7 +2618,8 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE ss->searchSpaceType->present); // Reset TPC to 0 dB to not request new gain multiple times before computing new value for SNR - UE->UE_sched_ctrl.tpc0 = 1; + cur_harq->sched_pusch.tpc_pusch = sched_ctrl->tpc0; + sched_ctrl->tpc0 = 1; fill_dci_pdu_rel15(&UE->sc_info, &UE->current_DL_BWP, diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index e995cc9da0..9677c6c2f4 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -469,6 +469,9 @@ typedef struct NR_sched_pusch { NR_pusch_dmrs_t dmrs_info; bwp_info_t bwp_info; int phr_txpower_calc; + + /// TPC command for this PUSCH + int tpc_pusch; } NR_sched_pusch_t; typedef struct NR_pdsch_dmrs { diff --git a/tools/plots/README.md b/tools/plots/README.md index 39f9fab6f1..a83ad93f8d 100644 --- a/tools/plots/README.md +++ b/tools/plots/README.md @@ -32,3 +32,43 @@ The plots are saved in three formats: `.png`, `.pdf` and `.svg`. ### Example graphs: #### BER ![image](./ber_compare.svg) + + +## `plot_power_control.sh` + +This plots the relevant graphs for power control at the gNB, exported through T +and recorded via the [`csv` tracer](../../common/utils/T/DOC/T/csv.md) and +plotted with `gnuplot`. + +Run first the `csv` tracer in one terminal, selecting the desired quantities, and +write them out to a file: + + ./common/utils/T/tracer/csv -d ../common/utils/T/T_messages.txt -s $'\t' -t time GNB_MAC_PUSCH_POWER_CONTROL time snrx10 phr tpc tb_size txpower_calc rbSize mcs > /tmp/pusch.csv + +in a second terminal, run the gNB with desired configuration, and pass the +`--T_stdout 2` option to connect to the `csv` tracer: + + sudo ./nr-softmodem -O --T_stdout 2 + +Note that you could also run both in one window, connecting both commands with +a `&`. In that case, it's important to run the `nr-softmodem` at second +position, which will receive signals and stop the `csv` tracer when stopping +the main process. + +After recovering the `pusch.csv` file, you could plot it with a script. + + ./plot-power-control.gp.sh /tmp/pusch.csv + +The script prints four graphs: + +1. prints instantaneous SNR, and "deltaMCS" (the power used for the + transmission if in deltaMCS mode that accounts for MCS). It also prints an + average SNR, but this has to be manually post-processed from the instanteous + SNR values (`snrx10` above) +2. TPC commands and PHR of the UE +3. Resource blocks used for all transmissions, and the MCS +4. transport block size of the transmission, to estimate the amount of traffic. + +The script should either auto-scale graphs or have sensible defaults. It prints +on screen. There is code to print to a file; uncomment the relevant lines to +achieve this. diff --git a/tools/plots/plot-power-control.gp.sh b/tools/plots/plot-power-control.gp.sh new file mode 100755 index 0000000000..33329ad212 --- /dev/null +++ b/tools/plots/plot-power-control.gp.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +function die() { echo "$@"; exit 1; } + +[[ $# == 1 ]] || die "usage: $0 " + +FILE=${1} + +#OUTPUT=/tmp/results.png +#echo "dumping to $OUTPUT" +gnuplot << EOF + +#set terminal pngcairo size 2000,2000 enhanced font 'Verdana,10' +#set output "$OUTPUT" + +set format x '%H:%M:%S' +set timefmt '%H:%M:%S' +set datafile separator "\t" +set xdata time +#set xrange ['08:43:17':'08:43:18'] +set grid + +set multiplot layout 4,1 + +set yrange [0:300] +set ylabel "dB" +plot "$FILE" using 1:2 w l title "SNRx10 (inst)", "$FILE" using 1:9 w l title "SNRx10 (smooth)", "$FILE" using 1:6 w l title "txpower-calc" + +set yrange [5:50] +set ylabel "PHR (dB)" +set y2range [0:3] +set y2label "TPC" +set boxwidth 0.1 +plot "$FILE" using 1:4 w points pt 7 axes x1y2 title "TPC", "$FILE" using 1:3 w l title "PHR" + +set yrange [0:60] +set ylabel "RB" +set y2range [0:27] +set y2label "MCS" +plot "$FILE" using 1:7 w l title "RB", "$FILE" using 1:8 w l axes x1y2 title "MCS" + +set yrange [0:1000] +set ylabel "TB size (B)" +plot "$FILE" using 1:5 w l + +unset multiplot + +pause mouse close +EOF From c229d59efdf8e948fdb2ab399a3631f70cadb92b Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 24 Feb 2026 18:44:27 +0100 Subject: [PATCH 06/17] Add PUCCH power control T traces --- common/utils/T/T_messages.txt | 4 ++++ openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c | 16 ++++++++++++++++ tools/plots/README.md | 8 ++++++++ 3 files changed, 28 insertions(+) diff --git a/common/utils/T/T_messages.txt b/common/utils/T/T_messages.txt index c763c691a5..918982a8c8 100644 --- a/common/utils/T/T_messages.txt +++ b/common/utils/T/T_messages.txt @@ -217,6 +217,10 @@ ID = GNB_MAC_PUSCH_POWER_CONTROL DESC = NR MAC PUSCH power control-related data GROUP = ALL:MAC:GNB:CSV FORMAT = int,rnti : int,frame : int,slot : int,snrx10 : int,phr : int,tpc : int,tb_size : int,txpower_calc : int,rbSize : int,mcs : int,rssi +ID = GNB_MAC_PUCCH_POWER_CONTROL + DESC = NR MAC PUCCH power control-related data + GROUP = ALL:MAC:GNB:CSV + FORMAT = int,rnti : int,frame : int,slot : int,snrx10 : int,tpc : int,rssi ID = GNB_MAC_DL_RAR_PDU_WITH_DATA DESC = NR MAC downlink PDU for RAR GROUP = ALL:MAC:GNB:WIRESHARK diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c index 062bdfc221..21fd5b1776 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c @@ -936,6 +936,14 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con } else sched_ctrl->tpc1 = 1; sched_ctrl->tpc1 = nr_limit_tpc(sched_ctrl->tpc1, uci_01->rssi, rssi_threshold); + + T(T_GNB_MAC_PUCCH_POWER_CONTROL, + T_INT(uci_01->rnti), + T_INT(frame), + T_INT(slot), + T_INT(sched_ctrl->pucch_snrx10), + T_INT(sched_ctrl->tpc1), + T_INT(uci_01->rssi)); } // check scheduling request result, confidence_level == 0 is good @@ -971,6 +979,14 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c sched_ctrl->pucch_snrx10 = uci_234->ul_cqi * 5 - 640; sched_ctrl->tpc1 = nr_get_tpc(nrmac->radio_config.pucch.target_snrx10, uci_234->ul_cqi, 30, 0); sched_ctrl->tpc1 = nr_limit_tpc(sched_ctrl->tpc1, uci_234->rssi, rssi_threshold); + + T(T_GNB_MAC_PUCCH_POWER_CONTROL, + T_INT(uci_234->rnti), + T_INT(frame), + T_INT(slot), + T_INT(sched_ctrl->pucch_snrx10), + T_INT(sched_ctrl->tpc1), + T_INT(uci_234->rssi)); } // TODO: handle SR diff --git a/tools/plots/README.md b/tools/plots/README.md index a83ad93f8d..a04043d097 100644 --- a/tools/plots/README.md +++ b/tools/plots/README.md @@ -55,6 +55,14 @@ a `&`. In that case, it's important to run the `nr-softmodem` at second position, which will receive signals and stop the `csv` tracer when stopping the main process. +The script might also be used to print PUCCH SNR. This can be achieved by only +recording the relevant fields with the `csv` tracer for trace +`GNB_MAC_PUCCH_POWER_CONTROL + + ./common/utils/T/tracer/record -d ../common/utils/T/T_messages.txt -OFF -on GNB_MAC_PUSCH_POWER_CONTROL -on GNB_MAC_PUCCH_POWER_CONTROL -o /tmp/record.raw + +Use the `replay` tracer and `csv` to separate into PUSCH and PUCCH traces. + After recovering the `pusch.csv` file, you could plot it with a script. ./plot-power-control.gp.sh /tmp/pusch.csv From 6fbaaa02a0685fba50e3bc19912b92df9f2d9cf9 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 31 Oct 2025 19:21:34 +0100 Subject: [PATCH 07/17] Fix deltaMCS scheduling mode: save phr_txpower_calc The phr_txpower_calc field actually needs to be saved in the HARQ process for later retrieval, so set sched_pusch after phr_txpower_calc has been calculated. --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 63 ++++++++++--------- 1 file changed, 32 insertions(+), 31 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index 58fee1c8b8..f5c07838da 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -2466,9 +2466,6 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE /* Statistics */ AssertFatal(cur_harq->round < nr_mac->ul_bler.harq_round_max, "Indexing ulsch_rounds[%d] is out of bounds\n", cur_harq->round); UE->mac_stats.ul.rounds[cur_harq->round]++; - /* Save information on MCS, TBS etc for the current initial transmission - * so we have access to it when retransmitting */ - cur_harq->sched_pusch = *sched_pusch; if (cur_harq->round == 0) { UE->mac_stats.ulsch_total_bytes_scheduled += sched_pusch->tb_size; sched_ctrl->sched_ul_bytes += sched_pusch->tb_size; @@ -2483,34 +2480,6 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE sched_ctrl->last_ul_frame = sched_pusch->frame; sched_ctrl->last_ul_slot = sched_pusch->slot; - LOG_D(NR_MAC, - "ULSCH/PUSCH: %4d.%2d RNTI %04x UL sched %4d.%2d DCI L %d start %2d RBS %3d TDA %2d dmrs_pos %x MCS " - "Table %2d MCS %2d nrOfLayers %2d num_dmrs_cdm_grps_no_data %2d TBS %4d HARQ PID %2d round %d RV %d NDI %d est %6d sched " - "%6d est BSR %6d TPC %d\n", - frame, - slot, - UE->rnti, - sched_pusch->frame, - sched_pusch->slot, - sched_ctrl->aggregation_level, - sched_pusch->rbStart, - sched_pusch->rbSize, - sched_pusch->time_domain_allocation, - sched_pusch->dmrs_info.ul_dmrs_symb_pos, - current_BWP->mcs_table, - sched_pusch->mcs, - sched_pusch->nrOfLayers, - sched_pusch->dmrs_info.num_dmrs_cdm_grps_no_data, - sched_pusch->tb_size, - harq_id, - cur_harq->round, - nr_get_rv(cur_harq->round % 4), - cur_harq->ndi, - sched_ctrl->estimated_ul_buffer, - sched_ctrl->sched_ul_bytes, - sched_ctrl->estimated_ul_buffer - sched_ctrl->sched_ul_bytes, - sched_ctrl->tpc0); - T(T_GNB_MAC_UL, T_INT(UE->rnti), T_INT(frame), T_INT(slot), T_INT(sched_pusch->mcs), T_INT(sched_pusch->tb_size)); /* PUSCH in a later slot, but corresponding DCI now! */ @@ -2558,6 +2527,38 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE deltaMCS, false); + LOG_D(NR_MAC, + "ULSCH/PUSCH: %4d.%2d RNTI %04x UL sched %4d.%2d DCI L %d start %2d RBS %3d TDA %2d dmrs_pos %x MCS " + "Table %2d MCS %2d nrOfLayers %2d num_dmrs_cdm_grps_no_data %2d TBS %4d HARQ PID %2d round %d RV %d NDI %d est %6d sched " + "%6d est BSR %6d TPC %d\n", + frame, + slot, + UE->rnti, + sched_pusch->frame, + sched_pusch->slot, + sched_ctrl->aggregation_level, + sched_pusch->rbStart, + sched_pusch->rbSize, + sched_pusch->time_domain_allocation, + sched_pusch->dmrs_info.ul_dmrs_symb_pos, + current_BWP->mcs_table, + sched_pusch->mcs, + sched_pusch->nrOfLayers, + sched_pusch->dmrs_info.num_dmrs_cdm_grps_no_data, + sched_pusch->tb_size, + harq_id, + cur_harq->round, + nr_get_rv(cur_harq->round % 4), + cur_harq->ndi, + sched_ctrl->estimated_ul_buffer, + sched_ctrl->sched_ul_bytes, + sched_ctrl->estimated_ul_buffer - sched_ctrl->sched_ul_bytes, + sched_ctrl->tpc0); + + /* Save information on MCS, TBS etc for the current initial transmission + * so we have access to it when retransmitting */ + cur_harq->sched_pusch = *sched_pusch; + /* a PDCCH PDU groups DCIs per BWP and CORESET. Save a pointer to each * allocated PDCCH so we can easily allocate UE's DCIs independent of any * CORESET order */ From 3d5edef27e4d8bf6a6253bc421b18c1a307a9605 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 31 Oct 2025 19:20:44 +0100 Subject: [PATCH 08/17] Remove unused PH factor calculation The same calculation is done in post_process_ulsch(), so we don't need it here. --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index f5c07838da..8f5fe57d94 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -2275,19 +2275,6 @@ static int pf_ul(gNB_MAC_INST *nrmac, >> 3; } - // Calacualte the normalized tx_power for PHR - long *deltaMCS = current_BWP->pusch_Config ? current_BWP->pusch_Config->pusch_PowerControl->deltaMCS : NULL; - int tbs_bits = sched.tb_size << 3; - - sched.phr_txpower_calc = compute_ph_factor(current_BWP->scs, - tbs_bits, - sched.rbSize, - sched.nrOfLayers, - sched.tda_info.nrOfSymbols, - sched.dmrs_info.N_PRB_DMRS * sched.dmrs_info.num_dmrs_symb, - deltaMCS, - false); - LOG_D(NR_MAC, "rbSize %d (available_rb %d), TBS %d, est buf %d, sched_ul %d, B %d, CCE %d, num_dmrs_symb %d, N_PRB_DMRS %d\n", sched.rbSize, From ffae59427a190896a22a23d9438498ddb0360913 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 31 Oct 2025 19:19:26 +0100 Subject: [PATCH 09/17] Logs for debugging --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index 8f5fe57d94..3b2c3cc2f7 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -986,7 +986,8 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, if (timing_advance != 0xffff) UE_scheduling_control->ta_update = timing_advance; - UE_scheduling_control->pusch_snrx10 = ul_cqi * 5 - 640 - (txpower_calc * 10); + int pusch_snrx10 = ul_cqi * 5 - 640; + UE_scheduling_control->pusch_snrx10 = pusch_snrx10 - (txpower_calc * 10); const NR_sched_pusch_t *sched_pusch = &UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch; (void) sched_pusch; // avoids warnings of unused sched_pusch when compiling without T T(T_GNB_MAC_PUSCH_POWER_CONTROL, T_INT(rntiP), T_INT(frameP), T_INT(slotP), @@ -998,20 +999,18 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, T_INT(sched_pusch->rbSize), T_INT(sched_pusch->mcs), T_INT(rssi)); - if (UE_scheduling_control->tpc0 > 1) - LOG_D(NR_MAC, - "[UE %04x] %d.%d. PUSCH TPC %d and TA %d pusch_snrx10 %d rssi %d phrx_tx_power %d PHR (1PRB) %d mcs %d, nb_rb %d\n", + if (false) + LOG_I(NR_MAC, + "[UE %04x] %4d.%2d. rssi %4d snr %3d txp_calc %3d PHR %2d RB %2d MCS %2d\n", UE->rnti, frameP, slotP, - UE_scheduling_control->tpc0, - UE_scheduling_control->ta_update, - UE_scheduling_control->pusch_snrx10, rssi, + pusch_snrx10 / 10, txpower_calc, UE_scheduling_control->ph, - UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.mcs, - UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.rbSize); + UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.rbSize, + UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.mcs); NR_UE_ul_harq_t *cur_harq = &UE_scheduling_control->ul_harq_processes[harq_pid]; if (cur_harq->round == 0) From db0f6696f4bf15a48e0e331fa496d1fcfbffd19c Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Fri, 23 Jan 2026 18:48:18 +0100 Subject: [PATCH 10/17] Reimplement (PUSCH) power control Introduce new data structures to track the average SNR and RSSI, and that allow to dynamically modify the target SNR (per UE) as well as continuously get TPC updates based on the average SNR. The target SNR is per power control loop (which a future commit will extend to use for PUCCH). Concretely, it maintains an average of measured SNR and RSSI. It also updates "tpc_in_flight", which tracks TPC changes that don't show up in the average yet. For instance, imagine that the target SNR of 15 changes to 20. Three successive TPC commands need to be sent (+3, +1, +1), but it will take time to show up in the average SNR. To account for this, tpc_in_flight is updated by the TPCs sent, and an average will make it go down back to zero at the same pace as the average SNR approaches the target SNR. The sum of average SNR and tpc_in_flight sums up to the actual, current SNR, which approximates the target SNR. The SNR is kept within -1<=targetSNR<=+2dB to avoid too many TPC changes. The current average SNR (sum of average SNR and tpc_in_flight) is used to continuously get TPC updates, taking into account recent TPCs sent, the difference to target SNR, and RSSI. If there is DTX, tpc_in_flight is modified to artificially introduce TPC commands which increase the currently used SNR of the UE. This is temporary, as tpc_in_flight eventually will go back to zero. If the DTX was due to sudden SNR drop (e.g., higher path loss), this will allow to quickly adapt the UE's SNR to new conditions. The periodical logs are extended to not only show the current SNR, but also the difference from the target for better analysis. Co-authored-by: Maxime Elkael --- doc/MAC/mac-usage.md | 9 +- .../RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c | 2 +- openair2/LAYER2/NR_MAC_gNB/config.c | 2 +- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c | 4 +- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 146 +++++++++++++++++- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 54 ++----- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 10 +- .../LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c | 2 +- openair2/LAYER2/NR_MAC_gNB/main.c | 8 +- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 13 +- 10 files changed, 189 insertions(+), 61 deletions(-) diff --git a/doc/MAC/mac-usage.md b/doc/MAC/mac-usage.md index 94c813d7ac..84150506c5 100644 --- a/doc/MAC/mac-usage.md +++ b/doc/MAC/mac-usage.md @@ -82,7 +82,7 @@ UE RNTI 2460 CU-UE-ID 2 in-sync PH 28 dB PCMAX 24 dBm, average RSRP -74 (8 meas) UE 2460: CQI 15, RI 2, PMI (14,1) UE 2460: UL-RI 2 TPMI 0 UE 2460: dlsch_rounds 32917/5113/1504/560, dlsch_errors 211, pucch0_DTX 1385, BLER 0.19557 MCS (1) 23 CCE fail 3 -UE 2460: ulsch_rounds 3756/353/182/179, ulsch_errors 170, ulsch_DTX 285, BLER 0.33021 MCS (1) 27 (Qm 8 dB) NPRB 5 SNR 31.0 dB CCE fail 0 +UE 2460: ulsch_rounds 3756/353/182/179, ulsch_errors 170, ulsch_DTX 285, BLER 0.33021 MCS (1) 27 (Qm 8 dB) NPRB 5 SNR 31.0 (-1.0) dB CCE fail 0 UE 2460: MAC: TX 1530943191 RX 194148 bytes UE 2460: LCID 1: TX 651 RX 3031 bytes UE 2460: LCID 2: TX 0 RX 0 bytes @@ -162,10 +162,9 @@ The fourth and fifth line show HARQ-related information: fluctuate a lot but when doing high throughput with iperf, it indicates the number of PRBs that the UE is actually able to use with its power budget and should be high. -* ULSCH `SNR`: the current SNR that the gNB receives the UE signal with. It - should be close to the target in the gNB configuration file, - `pusch_TargetSNRx10`, which should be around 10 times the value shown in the - log +* ULSCH `SNR`: the current SNR that the gNB receives the UE PUSCH signal with. + This value should be close to the target SNR; in paranthesis, the difference + to the target SNR. * Both ULSCH/DLSCH `CCE fail`: lists the number of failed CCE attempts. If this number gets high, it signifies that the scheduler tried to scheduled this UE, but could not allocate the DCI. diff --git a/openair2/E2AP/RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c b/openair2/E2AP/RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c index 13b6306a1c..2778913524 100644 --- a/openair2/E2AP/RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c +++ b/openair2/E2AP/RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c @@ -81,7 +81,7 @@ bool read_mac_sm(void* data) rd->dl_aggr_sdus = UE->mac_stats.dl.num_mac_sdu; rd->ul_aggr_sdus = UE->mac_stats.ul.num_mac_sdu; - rd->pusch_snr = (float) sched_ctrl->pusch_snrx10 / 10; //: float = -64; + rd->pusch_snr = sched_ctrl->pusch_pc.avg_snr; //: float = -64; rd->pucch_snr = (float) sched_ctrl->pucch_snrx10 / 10; //: float = -64; rd->wb_cqi = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb; diff --git a/openair2/LAYER2/NR_MAC_gNB/config.c b/openair2/LAYER2/NR_MAC_gNB/config.c index bc402e7125..a6b09bb53b 100644 --- a/openair2/LAYER2/NR_MAC_gNB/config.c +++ b/openair2/LAYER2/NR_MAC_gNB/config.c @@ -1143,7 +1143,7 @@ bool nr_mac_add_test_ue(gNB_MAC_INST *nrmac, uint32_t rnti, NR_CellGroupConfig_t DevAssert(get_softmodem_params()->phy_test); NR_SCHED_LOCK(&nrmac->sched_lock); - NR_UE_info_t *UE = get_new_nr_ue_inst(&nrmac->UE_info.uid_allocator, rnti, CellGroup); + NR_UE_info_t *UE = get_new_nr_ue_inst(&nrmac->UE_info.uid_allocator, rnti, CellGroup, &nrmac->radio_config); DevAssert(UE->uid < MAX_MOBILES_PER_GNB); // physical simulators: we assume we can always create a UE free_and_zero(UE->ra); // physical simulators: UE will not do RA UE->local_bwp_id = 1; // for physical simulators diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c index fc563b56b9..e554fbe21c 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c @@ -604,7 +604,7 @@ int nr_fill_successrar(const NR_UE_sched_ctrl_t *ue_sched_ctl, successRAR->CONT_RES_6 = ue_cont_res_id[5]; successRAR->R = 0; successRAR->CH_ACESS_CPEXT = 1; - successRAR->TPC = ue_sched_ctl->tpc0; + successRAR->TPC = 1; // 0dB change, don't know how to determine this. successRAR->HARQ_FTI = timing_indicator; successRAR->PUCCH_RI = resource_indicator; successRAR->TA1 = (uint8_t)(timing_advance_cmd >> 8); // 4 MSBs of timing advance; @@ -718,7 +718,7 @@ void nr_initiate_ra_proc(module_id_t module_idP, return; } - UE = get_new_nr_ue_inst(&nr_mac->UE_info.uid_allocator, rnti, NULL); + UE = get_new_nr_ue_inst(&nr_mac->UE_info.uid_allocator, rnti, NULL, &nr_mac->radio_config); if (!add_new_UE_RA(nr_mac, UE)) { LOG_E(NR_MAC, "FAILURE: %4d.%2d initiating RA procedure for preamble index %d: no free RA process\n", frame, slot, preamble_index); delete_nr_ue_data(UE, NULL, &nr_mac->UE_info.uid_allocator); diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 7b9a32f547..0cffa53b09 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -2973,7 +2973,7 @@ static void init_bler_stats(const NR_bler_options_t *bler_options, NR_bler_stats * It will be typically added to the access_ue_list, but not always (e.g., * phytest mode), so this is not done in this function (and also, to allow * error handling). Remove with delete_nr_ue_data(). */ -NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroupConfig_t *CellGroup) +NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroupConfig_t *CellGroup, const nr_mac_config_t *config) { NR_UE_info_t *UE = calloc_or_fail(1, sizeof(NR_UE_info_t)); UE->uid = uid_linear_allocator_new(uia); @@ -2983,6 +2983,10 @@ NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroup NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; sched_ctrl->ta_update = 31; + nr_mac_set_target_snrx10(&sched_ctrl->pusch_pc, config->pusch.target_snrx10); + sched_ctrl->pusch_pc.avg_snr = config->pusch.target_snrx10 / 10.0f; // set initial SNR to what we would expect on average + nr_mac_set_rssi_threshold(&sched_ctrl->pusch_pc, config->pusch.rssi_threshold); + /* Set default BWPs */ AssertFatal(UE->sc_info.n_ul_bwp <= NR_MAX_NUM_BWP, "uplinkBWP_ToAddModList has %d BWP!\n", UE->sc_info.n_ul_bwp); @@ -3134,6 +3138,19 @@ uint8_t nr_get_tpc(int target, uint8_t cqi, int incr, int tx_power) return 1; // no change } +/** + * @brief Returns the number of dBs for given transmit power control (TPC) + * command. Asserts on illegal TPC. + * @param tpc TPC command in range 0..3 + * @return The dB value expressed by the TPC command + */ +static int tpc_to_db(int tpc) +{ + DevAssert(tpc >= 0 && tpc <= 3); + const int db[] = {-1, 0, 1, 3}; + return db[tpc]; +} + /** * @brief Limits the power control commands (TPC) in NR by checking the RSSI threshold. * @@ -3156,11 +3173,10 @@ uint8_t nr_limit_tpc(int tpc, int rssi, int rssi_threshold) const int fapi_rssi_0dBm_or_0dBFS = 1280; int rssi_fapi_threshold = fapi_rssi_0dBm_or_0dBFS + rssi_threshold; // Further limit TPC if above or near RSSI threshold - int tpc_to_db[] = {-1, 0, 1, 3}; if (rssi > rssi_fapi_threshold) { // RSSI above theshold, reduce power return 0; - } else if (rssi + tpc_to_db[tpc] * 10 > rssi_fapi_threshold) { + } else if (rssi + tpc_to_db(tpc) * 10 > rssi_fapi_threshold) { // Cannot apply required TPC, check 1 dB increment if (rssi + 10 > rssi_fapi_threshold) { // Still cannot apply required TPC, keep power @@ -4134,3 +4150,127 @@ void nr_mac_update_pdcch_closed_loop_adjust(NR_UE_sched_ctrl_t *sched_ctrl, bool sched_ctrl->pdcch_cl_adjust = max(0, sched_ctrl->pdcch_cl_adjust - 0.01); } } + +/** + * @brief Calculate the current average SNR for the given power control loop. + * @param pc the power control loop + * @return the current average SNR + */ +float nr_mac_get_snr(const nr_power_control_t *pc) +{ + return pc->avg_snr + pc->tpc_in_flight; +} + +/** + * @brief Calculates the difference of current average SNR to the target SNR + * set in the power control loop. + * @param pc the power control loop + * @return the SNR difference + */ +static float get_snr_diff(const nr_power_control_t *pc) +{ + float snr = nr_mac_get_snr(pc); + float delta = (snr * 10.0f - pc->target_snrx10) / 10.0f; + LOG_D(NR_MAC, "target %.2f snr %.2f delta %.2f\n", pc->target_snrx10 / 10.0f, snr, delta); + return delta; +} + +/// averaging constant for power control (used for SNR, RSSI). +#define PC_AVG_CNST 0.975f +/** + * @brief Enter new SNR and RSSI value for the latest UL transmission, and + * update the average SNR and average RSSI of the corresponding UE. + * + * Since the SNR is averaged, any changes through transmit power control (TPC) + * commands will take time to reflect in the averaged SNR. To allow a + * continuous tracking of necessary TPC, as well as a continuous adjustment of + * the target SNR, the algorithm keeps track of "TPC in flight" which is a + * moving average of the last TPC commands sent to the UE. The sum of the + * averaged SNR and "TPC in flight" is the actual, current SNR. The RSSI in + * turn is used to limit TPC commands (see also nr_limit_tpc()). + * + * @param pc the power control loop + * @param snrx10 the current SNR measurement multiplied by 10 + * @param rssi the current RSSI measurement + */ +void nr_mac_pc_snr(nr_power_control_t *pc, int snrx10, int rssi) +{ + pc->avg_snr = PC_AVG_CNST * pc->avg_snr + (1.0f - PC_AVG_CNST) * 0.1 * snrx10; + pc->avg_rssi = PC_AVG_CNST * pc->avg_rssi + (1.0f - PC_AVG_CNST) * rssi; + // use an EMA to average out tpc_in_flight as fast as EMA for avg_snr. + // this will ensure that on TPC change, avg_snr approximates real SNR as + // fast as tpc_in_flight returns to 0. + pc->tpc_in_flight = PC_AVG_CNST * pc->tpc_in_flight; // + (1 - PC_AVG_CNST) * 0.0f +} + +/** + * @brief Set a new target SNR for this power control loop, which can be + * updated on a continuous basis, and will be reflected immediately upon + * calls to nr_mac_get_tpc() (no "settle time" necessary). + * + * @param pc the power control loop + * @param target_snrx10 the target SNR to be set times 10. + */ +void nr_mac_set_target_snrx10(nr_power_control_t *pc, int target_snrx10) +{ + pc->target_snrx10 = target_snrx10; +} + +/** + * @brif Set a new RSSI threshold for this power control loop. Will be used to + * limit TPCs, see also nr_limit_tpc(). + * + * @param pc the power control loop + * @param rssi_threshold the RSSI threshold + */ +void nr_mac_set_rssi_threshold(nr_power_control_t *pc, int rssi_threshold) +{ + pc->rssi_threshold = rssi_threshold; +} + +/** + * @brief Signal that this power control loop experienced a discontinuous + * transmission (DTC), aka "no reception".. This will decrease the "TPC in + * flight" to artificially reduce the current SNR, which will trigger a TPC + * command to increase the power. Repeated DTX will lead to more TPC. + * + * @param pc the power control loop + */ +void nr_mac_signal_dtx(nr_power_control_t *pc) +{ + // repeated DTX will make nr_mac_get_tpc() return positive TPCs, see + // nr_mac_pc_snr(). The more DTX, the more TPC increase, which will + // eventually zero out and return back to target SNR (change is temporary). + // Also, this will only send positive TPC changes. + pc->tpc_in_flight = max(pc->tpc_in_flight - 1.0f, -5.0f); // TODO threshold good? +} + +/** + * @brief Get a transmit power control (TPC) command for the given power + * control loop according to current and target SNR. This function can be + * called continuously on every UL transmission occasion, and keeps track of + * past TPC commands. It also limits TPC commands according to RSSI, see + * nr_limit_tpc() for more information. + * + * @param pc the power control loop + * @return the next TPC command. + */ +int nr_mac_get_tpc(nr_power_control_t *pc) +{ + float diff = get_snr_diff(pc); + int tpc = 1; + if (diff <= -3.0f) { + tpc = 3; // increase 3dB + } else if (diff <= -1.0f) { + tpc = 2; // increase 1dB + } else if (diff >= 2.0f) { + tpc = 0; // decrease 1dB + } else { + tpc = 1; // within -1<=target<=2dB => is ok. + } + tpc = nr_limit_tpc(tpc, pc->avg_rssi, pc->rssi_threshold); + int change = tpc_to_db(tpc); + pc->tpc_in_flight += change; + LOG_D(NR_MAC, "tpc %d => %d\n", tpc, change); + return tpc; +} diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index 3b2c3cc2f7..016423d7a2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -850,13 +850,11 @@ static void nr_rx_ra_sdu(const module_id_t mod_id, DevAssert(harq_pid >= 0 && harq_pid < 8); if (ul_cqi != 0xff) { NR_UE_ul_harq_t *harq = &UE_scheduling_control->ul_harq_processes[harq_pid]; - DevAssert(harq->sched_pusch.phr_txpower_calc == 0); - UE_scheduling_control->tpc0 = nr_get_tpc(target_snrx10, ul_cqi, 30, harq->sched_pusch.phr_txpower_calc); - UE_scheduling_control->pusch_snrx10 = ul_cqi * 5 - 640 - harq->sched_pusch.phr_txpower_calc * 10; + DevAssert(harq->sched_pusch.phr_txpower_calc == 0); // TODO remove from here + UE_scheduling_control->pusch_pc.avg_snr = 0.1 * (ul_cqi * 5 - 640); } if (timing_advance != 0xffff) UE_scheduling_control->ta_update = timing_advance; - LOG_D(NR_MAC, "[UE %04x] PUSCH TPC %d and TA %d\n", UE->rnti, UE_scheduling_control->tpc0, UE_scheduling_control->ta_update); LOG_D(NR_MAC, "[RAPROC] Received %s:\n", ra->ra_type == RA_2_STEP ? "MsgA-PUSCH" : "Msg3"); for (uint32_t k = 0; k < sdu_len; k++) { @@ -944,8 +942,6 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, gNB_MAC_INST *gNB_mac = RC.nrmac[gnb_mod_idP]; const int current_rnti = rntiP; LOG_D(NR_MAC, "rx_sdu for rnti %04x\n", current_rnti); - const int target_snrx10 = gNB_mac->radio_config.pusch.target_snrx10; - const int rssi_threshold = gNB_mac->radio_config.pusch.rssi_threshold; const int pusch_failure_thres = gNB_mac->radio_config.pusch.failure_thres; NR_UE_info_t *UE = find_nr_UE(&gNB_mac->UE_info, current_rnti); if (UE) { @@ -977,21 +973,18 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, int txpower_calc = UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.phr_txpower_calc; UE->mac_stats.deltaMCS = txpower_calc; UE->mac_stats.NPRB = UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.rbSize; - if (ul_cqi != 0xff) - UE_scheduling_control->tpc0 = nr_get_tpc(target_snrx10, ul_cqi, 30, txpower_calc); - if (UE_scheduling_control->ph < 0 && UE_scheduling_control->tpc0 > 1) - UE_scheduling_control->tpc0 = 1; - UE_scheduling_control->tpc0 = nr_limit_tpc(UE_scheduling_control->tpc0, rssi, rssi_threshold); + int pusch_snrx10 = ul_cqi * 5 - 640 - txpower_calc * 10; // TODO don't log T? + if (ul_cqi != 0xff) + nr_mac_pc_snr(&UE_scheduling_control->pusch_pc, pusch_snrx10, rssi); if (timing_advance != 0xffff) UE_scheduling_control->ta_update = timing_advance; - int pusch_snrx10 = ul_cqi * 5 - 640; - UE_scheduling_control->pusch_snrx10 = pusch_snrx10 - (txpower_calc * 10); + const NR_sched_pusch_t *sched_pusch = &UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch; (void) sched_pusch; // avoids warnings of unused sched_pusch when compiling without T T(T_GNB_MAC_PUSCH_POWER_CONTROL, T_INT(rntiP), T_INT(frameP), T_INT(slotP), - T_INT(UE_scheduling_control->pusch_snrx10), + T_INT(pusch_snrx10), T_INT(UE_scheduling_control->ph), T_INT(sched_pusch->tpc_pusch), T_INT(sched_pusch->tb_size), @@ -999,27 +992,6 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, T_INT(sched_pusch->rbSize), T_INT(sched_pusch->mcs), T_INT(rssi)); - if (false) - LOG_I(NR_MAC, - "[UE %04x] %4d.%2d. rssi %4d snr %3d txp_calc %3d PHR %2d RB %2d MCS %2d\n", - UE->rnti, - frameP, - slotP, - rssi, - pusch_snrx10 / 10, - txpower_calc, - UE_scheduling_control->ph, - UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.rbSize, - UE_scheduling_control->ul_harq_processes[harq_pid].sched_pusch.mcs); - - NR_UE_ul_harq_t *cur_harq = &UE_scheduling_control->ul_harq_processes[harq_pid]; - if (cur_harq->round == 0) - UE->mac_stats.pusch_snrx10 = UE_scheduling_control->pusch_snrx10; - LOG_D(NR_MAC, "[UE %04x] PUSCH TPC %d and TA %d\n",UE->rnti,UE_scheduling_control->tpc0,UE_scheduling_control->ta_update); - } - else{ - LOG_D(NR_MAC,"[UE %04x] Detected DTX : increasing UE TX power\n",UE->rnti); - UE_scheduling_control->tpc0 = 1; } #if defined(ENABLE_MAC_PAYLOAD_DEBUG) @@ -1046,6 +1018,7 @@ static void _nr_rx_sdu(const module_id_t gnb_mod_idP, if (ul_cqi == 0xff || ul_cqi <= 128) { UE->UE_sched_ctrl.pusch_consecutive_dtx_cnt++; UE->mac_stats.ulsch_DTX++; + nr_mac_signal_dtx(&UE_scheduling_control->pusch_pc); } if (!get_softmodem_params()->phy_test && UE->UE_sched_ctrl.pusch_consecutive_dtx_cnt >= pusch_failure_thres) { @@ -2092,7 +2065,7 @@ static int pf_ul(gNB_MAC_INST *nrmac, int selected_mcs; int nrOfLayers = get_ul_nrOfLayers(sched_ctrl, current_BWP->dci_format); if (bo->harq_round_max == 1) { - selected_mcs = get_mcs_from_SINRx10(current_BWP->mcs_table, sched_ctrl->pusch_snrx10, nrOfLayers); + selected_mcs = get_mcs_from_SINRx10(current_BWP->mcs_table, sched_ctrl->pusch_pc.avg_snr * 10, nrOfLayers); selected_mcs = min(max_mcs, selected_mcs); selected_mcs = max(bo->min_mcs, selected_mcs); sched_ctrl->ul_bler_stats.mcs = selected_mcs; @@ -2513,6 +2486,7 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE deltaMCS, false); + int tpc = nr_mac_get_tpc(&sched_ctrl->pusch_pc); LOG_D(NR_MAC, "ULSCH/PUSCH: %4d.%2d RNTI %04x UL sched %4d.%2d DCI L %d start %2d RBS %3d TDA %2d dmrs_pos %x MCS " "Table %2d MCS %2d nrOfLayers %2d num_dmrs_cdm_grps_no_data %2d TBS %4d HARQ PID %2d round %d RV %d NDI %d est %6d sched " @@ -2539,7 +2513,7 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE sched_ctrl->estimated_ul_buffer, sched_ctrl->sched_ul_bytes, sched_ctrl->estimated_ul_buffer - sched_ctrl->sched_ul_bytes, - sched_ctrl->tpc0); + tpc); /* Save information on MCS, TBS etc for the current initial transmission * so we have access to it when retransmitting */ @@ -2599,14 +2573,12 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE &sched_ctrl->srs_feedback, tpmi, sched_pusch->time_domain_allocation, - UE->UE_sched_ctrl.tpc0, + tpc, cur_harq->ndi, current_BWP, ss->searchSpaceType->present); - // Reset TPC to 0 dB to not request new gain multiple times before computing new value for SNR - cur_harq->sched_pusch.tpc_pusch = sched_ctrl->tpc0; - sched_ctrl->tpc0 = 1; + cur_harq->sched_pusch.tpc_pusch = tpc; fill_dci_pdu_rel15(&UE->sc_info, &UE->current_DL_BWP, diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index 5a59ed67af..2936c20bb4 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -321,7 +321,7 @@ bool transition_ra_connected_nr_ue(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE); bool add_connected_nr_ue(gNB_MAC_INST *nr_mac, NR_UE_info_t *UE); bool nr_check_Msg4_MsgB_Ack(module_id_t module_id, frame_t frame, slot_t slot, NR_UE_info_t *UE, bool success); void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti); -NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroupConfig_t *CellGroup); +NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroupConfig_t *CellGroup, const nr_mac_config_t *config); int nr_get_default_pucch_res(int pucch_ResourceCommon); nfapi_nr_pusch_pdu_t *prepare_pusch_pdu(nfapi_nr_ul_tti_request_t *future_ul_tti_req, const NR_UE_info_t *UE, @@ -502,4 +502,12 @@ void nr_mac_clean_cellgroup(NR_CellGroupConfig_t *cell_group); void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE_info_t *UE, NR_sched_pdsch_t *sched_pdsch); void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE_info_t *UE, NR_sched_pusch_t *sched_pusch); + +float nr_mac_get_snr(const nr_power_control_t *pc); +void nr_mac_pc_snr(nr_power_control_t *pc, int snrx10, int rssi); +void nr_mac_set_target_snrx10(nr_power_control_t *pc, int target_snrx10); +void nr_mac_set_rssi_threshold(nr_power_control_t *pc, int rssi_threshold); +void nr_mac_signal_dtx(nr_power_control_t *pc); +int nr_mac_get_tpc(nr_power_control_t *pc); + #endif /*__LAYER2_NR_MAC_PROTO_H__*/ diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c index d14696db74..19fe908906 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c +++ b/openair2/LAYER2/NR_MAC_gNB/mac_rrc_dl_handler.c @@ -560,7 +560,7 @@ static NR_UE_info_t *create_new_UE(gNB_MAC_INST *mac, uint32_t cu_id, const NR_C bool success = du_add_f1_ue_data(rnti, &new_ue_data); DevAssert(success); - NR_UE_info_t *UE = get_new_nr_ue_inst(&mac->UE_info.uid_allocator, rnti, NULL); + NR_UE_info_t *UE = get_new_nr_ue_inst(&mac->UE_info.uid_allocator, rnti, NULL, &mac->radio_config); AssertFatal(UE->uid < MAX_MOBILES_PER_GNB, "cannot create UE context, UE context setup failure not implemented\n"); NR_CellGroupConfig_t *cellGroupConfig = NULL; diff --git a/openair2/LAYER2/NR_MAC_gNB/main.c b/openair2/LAYER2/NR_MAC_gNB/main.c index 7b32665717..babbeb5e47 100644 --- a/openair2/LAYER2/NR_MAC_gNB/main.c +++ b/openair2/LAYER2/NR_MAC_gNB/main.c @@ -219,9 +219,11 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset for (int i = 1; i < gNB->ul_bler.harq_round_max; i++) output = st_append(output, end, "/%"PRIu64, stats->ul.rounds[i]); + float snr = nr_mac_get_snr(&sched_ctrl->pusch_pc); + float diff_target = (snr * 10.0f - sched_ctrl->pusch_pc.target_snrx10) / 10.0f; output = st_append(output, end, - ", ulsch_errors %"PRIu64", ulsch_DTX %d, BLER %.5f MCS (%d) %d (Qm %d deltaMCS %d dB) NPRB %d SNR %d.%d dB CCE fail %d\n", + ", ulsch_errors %"PRIu64", ulsch_DTX %d, BLER %.5f MCS (%d) %d (Qm %d deltaMCS %d dB) NPRB %d SNR %.1f (%+.1f) dB CCE fail %d\n", stats->ul.errors, stats->ulsch_DTX, sched_ctrl->ul_bler_stats.bler, @@ -230,8 +232,8 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset nr_get_Qm_ul(sched_ctrl->ul_bler_stats.mcs,UE->current_UL_BWP.mcs_table), UE->mac_stats.deltaMCS, UE->mac_stats.NPRB, - sched_ctrl->pusch_snrx10 / 10, - sched_ctrl->pusch_snrx10 % 10, + snr, + diff_target, sched_ctrl->ul_cce_fail); output = st_append(output, end, diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 9677c6c2f4..0d7b8db12f 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -616,6 +616,14 @@ typedef struct nr_lc_config { NR_QoS_config_t qos_config[NR_MAX_NUM_QFI]; } nr_lc_config_t; +typedef struct nr_power_control { + float avg_snr; /// average SNR (in dB) + int target_snrx10; /// UE-specific target SNR x10 + float avg_rssi; /// average RSSI + int rssi_threshold; /// UE-specific RSSI threshld in 0.1dBm/dBFS, range -1280 to 0 + float tpc_in_flight; /// TPCs applied by UE but not yet in average SNR +} nr_power_control_t; + /*! \brief scheduling control information set through an API */ typedef struct { /// CCE index and aggregation, should be coherent with cce_list @@ -665,9 +673,7 @@ typedef struct { uint16_t ta_frame; int16_t ta_update; bool ta_apply; - uint8_t tpc0; uint8_t tpc1; - int pusch_snrx10; int pucch_snrx10; int pusch_consecutive_dtx_cnt; int pucch_consecutive_dtx_cnt; @@ -711,6 +717,8 @@ typedef struct { // pdcch closed loop adjust for PDCCH aggregation level, range <0, 1> // 0 - good channel, 1 - bad channel float pdcch_cl_adjust; + + nr_power_control_t pusch_pc; } NR_UE_sched_ctrl_t; typedef struct NR_mac_dir_stats { @@ -737,7 +745,6 @@ typedef struct NR_mac_stats { int cumul_sinrx10; uint8_t num_sinr_meas; char srs_stats[50]; // Statistics may differ depending on SRS usage - int pusch_snrx10; int deltaMCS; int NPRB; } NR_mac_stats_t; From 70422dc3c3df9df31c26795b4f228fe54ec5d1de Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 10 Mar 2026 11:49:50 +0100 Subject: [PATCH 11/17] Use Msg3 PUSCH SNR measurements in new power control loop --- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index 016423d7a2..ace53f62a2 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -795,7 +795,14 @@ static void nr_rx_ra_sdu(const module_id_t mod_id, } } - const int target_snrx10 = mac->radio_config.pusch.target_snrx10; + if (ul_cqi != 0xff) { + NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; + // Msg3: reset average. If this fails (e.g., ul_cqi == 0xff) + // everything starts from predetermined value + sched_ctrl->pusch_pc.avg_snr = 0.1 * (ul_cqi * 5 - 640); + sched_ctrl->pusch_pc.avg_rssi = rssi; + } + if (!sdu) { // NACK if (cfra) // no Msg3 on CFRA, no problem return; @@ -807,7 +814,7 @@ static void nr_rx_ra_sdu(const module_id_t mod_id, return; if (ul_cqi != 0xff) - ra->msg3_TPC = nr_get_tpc(target_snrx10, ul_cqi, 30, 0); + ra->msg3_TPC = nr_mac_get_tpc(&UE->UE_sched_ctrl.pusch_pc); handle_msg3_failed_rx(mac, ra, rnti, mac->ul_bler.harq_round_max); return; @@ -848,11 +855,6 @@ static void nr_rx_ra_sdu(const module_id_t mod_id, NR_UE_sched_ctrl_t *UE_scheduling_control = &UE->UE_sched_ctrl; DevAssert(harq_pid >= 0 && harq_pid < 8); - if (ul_cqi != 0xff) { - NR_UE_ul_harq_t *harq = &UE_scheduling_control->ul_harq_processes[harq_pid]; - DevAssert(harq->sched_pusch.phr_txpower_calc == 0); // TODO remove from here - UE_scheduling_control->pusch_pc.avg_snr = 0.1 * (ul_cqi * 5 - 640); - } if (timing_advance != 0xffff) UE_scheduling_control->ta_update = timing_advance; From cd0051dea1f57a5857197902b0b99a0ac72bb3b6 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 3 Feb 2026 18:55:18 +0100 Subject: [PATCH 12/17] Split compute_ph_factor() function to save space --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c | 150 +++++++----------- 1 file changed, 54 insertions(+), 96 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c index ace53f62a2..bcbb00f762 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_ulsch.c @@ -168,30 +168,25 @@ bwp_info_t get_pusch_bwp_start_size(NR_UE_info_t *UE) return bwp_info; } -static int compute_ph_factor(int mu, int tbs_bits, int rb, int n_layers, int n_symbols, int n_dmrs, long *deltaMCS, bool include_bw) +float compute_ph_rb_factor(int mu, int rb) +{ + return roundf(10 * log10(rb << mu)); +} + +float compute_ph_mcs_factor(const NR_sched_pusch_t *pusch) { // 38.213 7.1.1 // if the PUSCH transmission is over more than one layer delta_tf = 0 - float delta_tf = 0; - if(deltaMCS != NULL && n_layers == 1) { - const int n_re = (NR_NB_SC_PER_RB * n_symbols - n_dmrs) * rb; - const float BPRE = (float) tbs_bits/n_re; //TODO change for PUSCH with CSI - const float f = pow(2, BPRE * 1.25); - const float beta = 1.0f; //TODO change for PUSCH with CSI - delta_tf = (10 * log10((f - 1) * beta)); - LOG_D(NR_MAC, - "PH factor delta_tf %f (n_re %d, n_rb %d, n_dmrs %d, n_symbols %d, tbs %d BPRE %f f %f)\n", - delta_tf, - n_re, - rb, - n_dmrs, - n_symbols, - tbs_bits, - BPRE, - f); - } - const float bw_factor = (include_bw) ? 10 * log10(rb << mu) : 0; - return ((int)roundf(delta_tf + bw_factor)); + if (pusch->nrOfLayers != 1) + return 0.0f; + + const NR_pusch_dmrs_t *dmrs = &pusch->dmrs_info; + const int n_dmrs = dmrs->num_dmrs_symb * dmrs->N_PRB_DMRS; + const int n_re = (NR_NB_SC_PER_RB * pusch->tda_info.nrOfSymbols - n_dmrs) * pusch->rbSize; + const float BPRE = (float)(pusch->tb_size << 3) / n_re; // TODO change for PUSCH with CSI + const float f = pow(2, BPRE * 1.25); + const float beta = 1.0f; // TODO change for PUSCH with CSI + return 10 * log10((f - 1) * beta); } /* \brief over-estimate the BSR index, given real_index. @@ -558,16 +553,10 @@ static int nr_process_mac_pdu(instance_t module_idP, PH = 38; } // in sched_ctrl we set normalized PH wrt MCS and PRBs - long *deltaMCS = ul_bwp->pusch_Config ? ul_bwp->pusch_Config->pusch_PowerControl->deltaMCS : NULL; - sched_ctrl->ph = PH - + compute_ph_factor(ul_bwp->scs, - sched_pusch->tb_size << 3, - sched_pusch->rbSize, - sched_pusch->nrOfLayers, - sched_pusch->tda_info.nrOfSymbols, // n_symbols - sched_pusch->dmrs_info.num_dmrs_symb * sched_pusch->dmrs_info.N_PRB_DMRS, // n_dmrs - deltaMCS, - true); + sched_ctrl->ph = PH + compute_ph_rb_factor(ul_bwp->scs, sched_pusch->rbSize); + bool hasDeltaMCS = ul_bwp->pusch_Config && ul_bwp->pusch_Config->pusch_PowerControl->deltaMCS; + if (hasDeltaMCS) + sched_ctrl->ph += compute_ph_mcs_factor(sched_pusch); /* 38.133 Table10.1.18.1-1 */ sched_ctrl->pcmax = PCMAX - 29; LOG_D(NR_MAC, @@ -1706,70 +1695,47 @@ static void nr_ue_max_mcs_min_rb(int mu, AssertFatal(*Rb >= minRb, "illegal Rb %d < minRb %d\n", *Rb, minRb); AssertFatal(*mcs >= 0 && *mcs <= 28, "illegal MCS %d\n", *mcs); - int tbs_bits = tbs << 3; + NR_sched_pusch_t pot = *sched_pusch; // "potential" PUSCH allocation + pot.tb_size = tbs; + pot.rbSize = *Rb; + pot.mcs = *mcs; + uint16_t R; uint8_t Qm; - update_ul_ue_R_Qm(*mcs, ul_bwp->mcs_table, ul_bwp->pusch_Config, &R, &Qm); + update_ul_ue_R_Qm(pot.mcs, ul_bwp->mcs_table, ul_bwp->pusch_Config, &R, &Qm); - long *deltaMCS = ul_bwp->pusch_Config ? ul_bwp->pusch_Config->pusch_PowerControl->deltaMCS : NULL; - tbs_bits = nr_compute_tbs(Qm, R, *Rb, - sched_pusch->tda_info.nrOfSymbols, - sched_pusch->dmrs_info.N_PRB_DMRS * sched_pusch->dmrs_info.num_dmrs_symb, - 0, // nb_rb_oh - 0, - sched_pusch->nrOfLayers); + bool hasDeltaMCS = ul_bwp->pusch_Config && ul_bwp->pusch_Config->pusch_PowerControl->deltaMCS; + int n_dmrs = pot.dmrs_info.N_PRB_DMRS * pot.dmrs_info.num_dmrs_symb; + pot.tb_size = nr_compute_tbs(Qm, R, pot.rbSize, pot.tda_info.nrOfSymbols, n_dmrs, 0, 0, pot.nrOfLayers) >> 3; + int tx_power = compute_ph_rb_factor(mu, pot.rbSize) + (hasDeltaMCS ? compute_ph_mcs_factor(&pot) : 0); - int tx_power = compute_ph_factor(mu, - tbs_bits, - *Rb, - sched_pusch->nrOfLayers, - sched_pusch->tda_info.nrOfSymbols, - sched_pusch->dmrs_info.N_PRB_DMRS * sched_pusch->dmrs_info.num_dmrs_symb, - deltaMCS, - true); - - while (ph_limit < tx_power && *Rb > minRb) { - (*Rb)--; - tbs_bits = nr_compute_tbs(Qm, R, *Rb, - sched_pusch->tda_info.nrOfSymbols, - sched_pusch->dmrs_info.N_PRB_DMRS * sched_pusch->dmrs_info.num_dmrs_symb, - 0, // nb_rb_oh - 0, - sched_pusch->nrOfLayers); - tx_power = compute_ph_factor(mu, - tbs_bits, - *Rb, - sched_pusch->nrOfLayers, - sched_pusch->tda_info.nrOfSymbols, - sched_pusch->dmrs_info.N_PRB_DMRS * sched_pusch->dmrs_info.num_dmrs_symb, - deltaMCS, - true); - LOG_D(NR_MAC, "Checking %d RBs, MCS %d, ph_limit %d, tx_power %d\n",*Rb,*mcs,ph_limit,tx_power); + // reduce first RBs till minimum RB to fulfill PHR constraint while staying + // spectrally efficient + while (ph_limit < tx_power && pot.rbSize > minRb) { + pot.rbSize--; + pot.tb_size = nr_compute_tbs(Qm, R, pot.rbSize, pot.tda_info.nrOfSymbols, n_dmrs, 0, 0, pot.nrOfLayers) >> 3; + tx_power = compute_ph_rb_factor(mu, pot.rbSize) + (hasDeltaMCS ? compute_ph_mcs_factor(&pot) : 0); + LOG_D(NR_MAC, "Checking %d RBs, MCS %d, ph_limit %d, tx_power %d\n", pot.rbSize, pot.mcs, ph_limit, tx_power); } - while (ph_limit < tx_power && *mcs > 0) { - (*mcs)--; - update_ul_ue_R_Qm(*mcs, ul_bwp->mcs_table, ul_bwp->pusch_Config, &R, &Qm); - tbs_bits = nr_compute_tbs(Qm, R, *Rb, - sched_pusch->tda_info.nrOfSymbols, - sched_pusch->dmrs_info.N_PRB_DMRS * sched_pusch->dmrs_info.num_dmrs_symb, - 0, // nb_rb_oh - 0, - sched_pusch->nrOfLayers); - tx_power = compute_ph_factor(mu, - tbs_bits, - *Rb, - sched_pusch->nrOfLayers, - sched_pusch->tda_info.nrOfSymbols, - sched_pusch->dmrs_info.N_PRB_DMRS * sched_pusch->dmrs_info.num_dmrs_symb, - deltaMCS, - true); - LOG_D(NR_MAC, "Checking %d RBs, MCS %d, ph_limit %d, tx_power %d\n",*Rb,*mcs,ph_limit,tx_power); + // if this was not enough, further reduce MCS till it is enough + while (ph_limit < tx_power && pot.mcs > 0) { + pot.mcs--; + update_ul_ue_R_Qm(pot.mcs, ul_bwp->mcs_table, ul_bwp->pusch_Config, &R, &Qm); + pot.tb_size = nr_compute_tbs(Qm, R, pot.rbSize, pot.tda_info.nrOfSymbols, n_dmrs, 0, 0, pot.nrOfLayers) >> 3; + tx_power = compute_ph_rb_factor(mu, pot.rbSize) + (hasDeltaMCS ? compute_ph_mcs_factor(&pot) : 0); + LOG_D(NR_MAC, "Checking %d RBs, MCS %d, ph_limit %d, tx_power %d\n", pot.rbSize, pot.mcs, ph_limit, tx_power); } if (ph_limit < tx_power) - LOG_D(NR_MAC, "Normalized power %d based on current resources (RBs %d, MCS %d) exceed reported PHR %d (normalized value)\n", - tx_power, *Rb, *mcs, ph_limit); + LOG_D(NR_MAC, + "Normalized power %d based on current resources (RBs %d, MCS %d) exceed reported PHR %d (normalized value)\n", + tx_power, + pot.rbSize, + pot.mcs, + ph_limit); + *Rb = pot.rbSize; + *mcs = pot.mcs; } static bool allocate_ul_retransmission(gNB_MAC_INST *nrmac, @@ -2477,16 +2443,8 @@ void post_process_ulsch(gNB_MAC_INST *nr_mac, post_process_pusch_t *pusch, NR_UE req->n_pdus += 1; // Calculate the normalized tx_power for PHR - long *deltaMCS = current_BWP->pusch_Config ? current_BWP->pusch_Config->pusch_PowerControl->deltaMCS : NULL; - int tbs_bits = pusch_pdu->pusch_data.tb_size << 3; - sched_pusch->phr_txpower_calc = compute_ph_factor(current_BWP->scs, - tbs_bits, - sched_pusch->rbSize, - sched_pusch->nrOfLayers, - sched_pusch->tda_info.nrOfSymbols, - sched_pusch->dmrs_info.N_PRB_DMRS * sched_pusch->dmrs_info.num_dmrs_symb, - deltaMCS, - false); + bool hasDeltaMCS = current_BWP->pusch_Config && current_BWP->pusch_Config->pusch_PowerControl->deltaMCS; + sched_pusch->phr_txpower_calc = hasDeltaMCS ? compute_ph_mcs_factor(sched_pusch) : 0; int tpc = nr_mac_get_tpc(&sched_ctrl->pusch_pc); LOG_D(NR_MAC, From 27a5a756854a9cfd1fe9b07a010479807bd4f79f Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 10 Mar 2026 08:47:47 +0100 Subject: [PATCH 13/17] Reuse new power control functionality for PUCCH --- .../RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c | 2 +- openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c | 5 +-- .../LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c | 1 + .../LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 6 +-- .../NR_MAC_gNB/gNB_scheduler_primitives.c | 21 +++------- .../LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c | 38 +++++++++---------- openair2/LAYER2/NR_MAC_gNB/mac_proto.h | 4 +- openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h | 3 +- 8 files changed, 33 insertions(+), 47 deletions(-) diff --git a/openair2/E2AP/RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c b/openair2/E2AP/RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c index 2778913524..421a7f5740 100644 --- a/openair2/E2AP/RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c +++ b/openair2/E2AP/RAN_FUNCTION/CUSTOMIZED/ran_func_mac.c @@ -82,7 +82,7 @@ bool read_mac_sm(void* data) rd->ul_aggr_sdus = UE->mac_stats.ul.num_mac_sdu; rd->pusch_snr = sched_ctrl->pusch_pc.avg_snr; //: float = -64; - rd->pucch_snr = (float) sched_ctrl->pucch_snrx10 / 10; //: float = -64; + rd->pucch_snr = sched_ctrl->pucch_pc.avg_snr; //: float = -64; rd->wb_cqi = sched_ctrl->CSI_report.cri_ri_li_pmi_cqi_report.wb_cqi_1tb; rd->dl_mcs1 = sched_ctrl->dl_bler_stats.mcs; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c index e554fbe21c..850cf77d07 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_RA.c @@ -1348,6 +1348,7 @@ static void prepare_dl_pdus(gNB_MAC_INST *nr_mac, prepare_dci_pdu(pdcch_pdu_rel15, scc, sched_ctrl->search_space, coreset, aggregation_level, CCEIndex, fapi_beam, rnti); pdcch_pdu_rel15->numDlDci++; + int tpc = 1; // 0dB change, don't know how to determine this. dci_pdu_rel15_t dci_payload = prepare_dci_dl_payload(nr_mac, UE, rnti_type, @@ -1355,6 +1356,7 @@ static void prepare_dl_pdus(gNB_MAC_INST *nr_mac, pdsch_pdu_rel15, sched_pdsch, pucch, + tpc, current_harq_pid, tb_scaling, false); @@ -1918,9 +1920,6 @@ static void nr_generate_Msg4_MsgB(module_id_t module_idP, tb_scaling, pduindex); - // Reset TPC to 0 dB to not request new gain multiple times before computing new value for SNR - sched_ctrl->tpc1 = 1; - // Add padding header and zero rest out if there is space left if (ra->mac_pdu_length < harq->tb_size) { NR_MAC_SUBHEADER_FIXED *padding = (NR_MAC_SUBHEADER_FIXED *) &buf[ra->mac_pdu_length]; diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c index c6d130cdaf..4c92faadbb 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_bch.c @@ -405,6 +405,7 @@ static void nr_fill_nfapi_dl_SIB_pdu(gNB_MAC_INST *gNB_mac, pdsch_pdu_rel15, pdsch, NULL, + 1, 0, 0, is_sib1); diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 2ee068121b..6a56612016 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -1095,6 +1095,7 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE harq->is_waiting = true; } UE->mac_stats.dl.rounds[harq->round]++; + int tpc = nr_mac_get_tpc(&sched_ctrl->pucch_pc); LOG_D(NR_MAC, "%4d.%2d [DLSCH/PDSCH/PUCCH] RNTI %04x DCI L %d start %3d RBs %3d startSymbol %2d nb_symbol %2d dmrspos %x MCS %2d nrOfLayers %d TBS %4d HARQ PID %2d round %d RV %d NDI %d dl_data_to_ULACK %d (%d.%d) PUCCH allocation %d TPC %d\n", frame, @@ -1117,7 +1118,7 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE pucch ? pucch->frame : 0, pucch ? pucch->ul_slot : 0, sched_pdsch->pucch_allocation, - sched_ctrl->tpc1); + tpc); DevAssert(sched_pdsch->rbSize > 0); const int bwp_id = current_BWP->bwp_id; @@ -1195,12 +1196,11 @@ void post_process_dlsch(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pdsch, NR_UE pdsch_pdu, sched_pdsch, pucch, + tpc, current_harq_pid, 0, false); - // Reset TPC to 0 dB to not request new gain multiple times before computing new value for SNR - sched_ctrl->tpc1 = 1; NR_PDSCH_Config_t *pdsch_Config = current_BWP->pdsch_Config; AssertFatal(pdsch_Config == NULL || pdsch_Config->resourceAllocation == NR_PDSCH_Config__resourceAllocation_resourceAllocationType1, diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c index 0cffa53b09..9205a9e705 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_primitives.c @@ -869,6 +869,7 @@ dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac, const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *pdsch_pdu, const NR_sched_pdsch_t *sched_pdsch, const NR_sched_pucch_t *pucch, + int tpc, int harq_pid, int tb_scaling, bool is_sib1) @@ -902,7 +903,7 @@ dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac, const NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; dci_payload.dmrs_sequence_initialization.val = pdsch_pdu->SCID; dci_payload.antenna_ports.val = sched_pdsch->dmrs_parms.dmrs_ports_id; - dci_payload.tpc = sched_ctrl->tpc1; + dci_payload.tpc = tpc; const NR_UE_harq_t *harq = &sched_ctrl->harq_processes[harq_pid]; AssertFatal(harq, "HARQ process should be available for DCI with RNTI %s\n", rnti_types(rnti_type)); dci_payload.harq_pid.val = harq_pid; @@ -2983,6 +2984,9 @@ NR_UE_info_t *get_new_nr_ue_inst(uid_allocator_t *uia, rnti_t rnti, NR_CellGroup NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl; sched_ctrl->ta_update = 31; + nr_mac_set_target_snrx10(&sched_ctrl->pucch_pc, config->pucch.target_snrx10); + sched_ctrl->pucch_pc.avg_snr = config->pucch.target_snrx10 / 10.0f; // set initial SNR to what we would expect on average + nr_mac_set_rssi_threshold(&sched_ctrl->pucch_pc, config->pucch.rssi_threshold); nr_mac_set_target_snrx10(&sched_ctrl->pusch_pc, config->pusch.target_snrx10); sched_ctrl->pusch_pc.avg_snr = config->pusch.target_snrx10 / 10.0f; // set initial SNR to what we would expect on average nr_mac_set_rssi_threshold(&sched_ctrl->pusch_pc, config->pusch.rssi_threshold); @@ -3125,19 +3129,6 @@ void mac_remove_nr_ue(gNB_MAC_INST *nr_mac, rnti_t rnti) nr_release_ra_UE(nr_mac, rnti); } -// all values passed to this function are in dB x10 -uint8_t nr_get_tpc(int target, uint8_t cqi, int incr, int tx_power) -{ - // al values passed to this function are x10 - int snrx10 = (cqi * 5) - 640 - (tx_power * 10); - LOG_D(NR_MAC, "tpc : target %d, cqi %d, snrx10 %d, tx_power %d\n", target, ((int)cqi * 5) - 640, snrx10, tx_power); - if (snrx10 > target + incr) return 0; // decrease 1dB - if (snrx10 < target - (3*incr)) return 3; // increase 3dB - if (snrx10 < target - incr) return 2; // increase 1dB - LOG_D(NR_MAC,"tpc : target %d, snrx10 %d\n",target,snrx10); - return 1; // no change -} - /** * @brief Returns the number of dBs for given transmit power control (TPC) * command. Asserts on illegal TPC. @@ -3163,7 +3154,7 @@ static int tpc_to_db(int tpc) * @param rssi_threshold RSSI threshold in 0.1 dBm/dBFS, range -1280 to 0 * @return The adjusted TPC command after applying the RSSI threshold check. */ -uint8_t nr_limit_tpc(int tpc, int rssi, int rssi_threshold) +static uint8_t nr_limit_tpc(int tpc, int rssi, int rssi_threshold) { if (rssi == 0xFFFF) { // RSSI not available, keep tpc diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c index 21fd5b1776..2642d7704e 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_uci.c @@ -879,7 +879,6 @@ static NR_UE_harq_t *find_harq(frame_t frame, slot_t slot, NR_UE_info_t * UE, in void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, const nfapi_nr_uci_pucch_pdu_format_0_1_t *uci_01) { gNB_MAC_INST *nrmac = RC.nrmac[mod_id]; - int rssi_threshold = nrmac->radio_config.pucch.rssi_threshold; NR_SCHED_LOCK(&nrmac->sched_lock); NR_UE_info_t *UE = find_nr_UE(&nrmac->UE_info, uci_01->rnti); bool is_ra = false; @@ -925,25 +924,26 @@ void handle_nr_uci_pucch_0_1(module_id_t mod_id, frame_t frame, slot_t slot, con return; } } - if (harq_confidence == 1) + if (harq_confidence == 1) { UE->mac_stats.pucch0_DTX++; + // DTX for each _bit_, but maybe once for PUCCH is enough? not sure + nr_mac_signal_dtx(&sched_ctrl->pucch_pc); + } } // tpc (power control) only if we received AckNack if (uci_01->harq.harq_confidence_level == 0 && uci_01->ul_cqi != 0xff) { - sched_ctrl->pucch_snrx10 = uci_01->ul_cqi * 5 - 640; - sched_ctrl->tpc1 = nr_get_tpc(nrmac->radio_config.pucch.target_snrx10, uci_01->ul_cqi, 30, 0); - } else - sched_ctrl->tpc1 = 1; - sched_ctrl->tpc1 = nr_limit_tpc(sched_ctrl->tpc1, uci_01->rssi, rssi_threshold); + int pucch_snrx10 = uci_01->ul_cqi * 5 - 640; + nr_mac_pc_snr(&sched_ctrl->pucch_pc, pucch_snrx10, uci_01->rssi); - T(T_GNB_MAC_PUCCH_POWER_CONTROL, - T_INT(uci_01->rnti), - T_INT(frame), - T_INT(slot), - T_INT(sched_ctrl->pucch_snrx10), - T_INT(sched_ctrl->tpc1), - T_INT(uci_01->rssi)); + T(T_GNB_MAC_PUCCH_POWER_CONTROL, + T_INT(uci_01->rnti), + T_INT(frame), + T_INT(slot), + T_INT(pucch_snrx10), + T_INT(1), + T_INT(uci_01->rssi)); + } } // check scheduling request result, confidence_level == 0 is good @@ -962,7 +962,6 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c { gNB_MAC_INST *nrmac = RC.nrmac[mod_id]; NR_SCHED_LOCK(&nrmac->sched_lock); - int rssi_threshold = nrmac->radio_config.pucch.rssi_threshold; NR_UE_info_t *UE = find_nr_UE(&nrmac->UE_info, uci_234->rnti); if (!UE) { @@ -976,16 +975,15 @@ void handle_nr_uci_pucch_2_3_4(module_id_t mod_id, frame_t frame, slot_t slot, c // tpc (power control) // TODO PUCCH2 SNR computation is not correct -> ignore the following if (uci_234->ul_cqi != 0xff) { - sched_ctrl->pucch_snrx10 = uci_234->ul_cqi * 5 - 640; - sched_ctrl->tpc1 = nr_get_tpc(nrmac->radio_config.pucch.target_snrx10, uci_234->ul_cqi, 30, 0); - sched_ctrl->tpc1 = nr_limit_tpc(sched_ctrl->tpc1, uci_234->rssi, rssi_threshold); + int pucch_snrx10 = uci_234->ul_cqi * 5 - 640; + nr_mac_pc_snr(&sched_ctrl->pucch_pc, pucch_snrx10, uci_234->rssi); T(T_GNB_MAC_PUCCH_POWER_CONTROL, T_INT(uci_234->rnti), T_INT(frame), T_INT(slot), - T_INT(sched_ctrl->pucch_snrx10), - T_INT(sched_ctrl->tpc1), + T_INT(pucch_snrx10), + T_INT(1), T_INT(uci_234->rssi)); } diff --git a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h index 2936c20bb4..f2ab3934e0 100644 --- a/openair2/LAYER2/NR_MAC_gNB/mac_proto.h +++ b/openair2/LAYER2/NR_MAC_gNB/mac_proto.h @@ -169,6 +169,7 @@ dci_pdu_rel15_t prepare_dci_dl_payload(const gNB_MAC_INST *gNB_mac, const nfapi_nr_dl_tti_pdsch_pdu_rel15_t *pdsch_pdu, const NR_sched_pdsch_t *sched_pdsch, const NR_sched_pucch_t *pucch, + int tpc, int harq_pid, int tb_scaling, bool is_sib1); @@ -285,9 +286,6 @@ NR_pusch_dmrs_t get_ul_dmrs_params(const NR_ServingCellConfigCommon_t *scc, const NR_tda_info_t *tda_info, const int Layers); -uint8_t nr_get_tpc(int target, uint8_t cqi, int incr, int tx_power); -uint8_t nr_limit_tpc(int tpc, int rssi, int rssi_threshold); - int get_spf(nfapi_nr_config_request_scf_t *cfg); int to_absslot(nfapi_nr_config_request_scf_t *cfg,int frame,int slot); diff --git a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h index 0d7b8db12f..dc0345addc 100644 --- a/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h +++ b/openair2/LAYER2/NR_MAC_gNB/nr_mac_gNB.h @@ -673,8 +673,6 @@ typedef struct { uint16_t ta_frame; int16_t ta_update; bool ta_apply; - uint8_t tpc1; - int pucch_snrx10; int pusch_consecutive_dtx_cnt; int pucch_consecutive_dtx_cnt; bool ul_failure; @@ -719,6 +717,7 @@ typedef struct { float pdcch_cl_adjust; nr_power_control_t pusch_pc; + nr_power_control_t pucch_pc; } NR_UE_sched_ctrl_t; typedef struct NR_mac_dir_stats { From 83e2e7760dab17207051e51ee5f0d5c682dc4128 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Wed, 11 Mar 2026 10:37:43 +0100 Subject: [PATCH 14/17] Telnet helper to set PUSCH target SNR --- common/utils/telnetsrv/telnetsrv_ci.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/common/utils/telnetsrv/telnetsrv_ci.c b/common/utils/telnetsrv/telnetsrv_ci.c index 084e3b93f3..fdef246496 100644 --- a/common/utils/telnetsrv/telnetsrv_ci.c +++ b/common/utils/telnetsrv/telnetsrv_ci.c @@ -393,6 +393,27 @@ static int trigger_bwp_switch(char *buf, int debug, telnet_printfunc_t prnt) } } +static int set_pusch_target_snr(char *buf, int debug, telnet_printfunc_t prnt) +{ + if (!buf) + ERROR_MSG_RET("need an SNR to read\n"); + + char *end; + long new_snr = strtol(buf, &end, 0); + if (*end != 0) + ERROR_MSG_RET("error: could not parse number in '%s'\n", buf); + + gNB_MAC_INST *nrmac = RC.nrmac[0]; + NR_SCHED_LOCK(&nrmac->sched_lock); + UE_iterator(nrmac->UE_info.connected_ue_list, it) { + nr_mac_set_target_snrx10(&it->UE_sched_ctrl.pusch_pc, new_snr * 10); + } + NR_SCHED_UNLOCK(&nrmac->sched_lock); + prnt("set new PUSCH target SNR %d for all UEs\n", new_snr); + + return 0; +} + static telnetshell_cmddef_t cicmds[] = { {"get_single_rnti", "", get_single_rnti}, {"force_reestab", "[rnti(hex,opt)]", trigger_reestab}, @@ -404,6 +425,7 @@ static telnetshell_cmddef_t cicmds[] = { {"get_current_bwp", "[rnti(hex,opt)]", get_current_bwp}, {"trigger_bwp_switch", "newBWPId [rnti(hex,opt)]", trigger_bwp_switch}, {"trigger_n2_ho", "[neighbour_pci(uint32_t),ueId(uint32_t)]", rrc_gNB_trigger_n2_ho}, + {"set_pusch_target_snr", "[somelongSNR(dec)]", set_pusch_target_snr}, {"pdu_session_release", "[gNB_ue_ngap_id(int,opt)]", trigger_ngap_pdu_session_release}, {"", "", NULL}, }; From 73498ccae8ed8918f11a7857aa7b7d121f042d60 Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 16 Mar 2026 13:44:47 +0100 Subject: [PATCH 15/17] Update 100MHz 2x2 test case for UL throughput Do not limit DL power, because it is not done in the other pipelines either. Set the UL target power high, as this increases the achievable throughput high (PUCCH can be normal). Finally, increase BLER thresholds to improve the MCS a bit. --- .../conf_files/gnb.sa.band77.273prb.usrpn310.2x2.conf | 8 +++++--- .../xml_files/container_sa_n310_2X2_100MHz_quectel.xml | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/ci-scripts/conf_files/gnb.sa.band77.273prb.usrpn310.2x2.conf b/ci-scripts/conf_files/gnb.sa.band77.273prb.usrpn310.2x2.conf index 5928ea4452..1b871371be 100644 --- a/ci-scripts/conf_files/gnb.sa.band77.273prb.usrpn310.2x2.conf +++ b/ci-scripts/conf_files/gnb.sa.band77.273prb.usrpn310.2x2.conf @@ -172,9 +172,11 @@ MACRLCs = ( num_cc = 1; tr_s_preference = "local_L1"; tr_n_preference = "local_RRC"; - pusch_TargetSNRx10 = 270; - pucch_TargetSNRx10 = 270; + pusch_TargetSNRx10 = 320; + pucch_TargetSNRx10 = 200; pusch_FailureThres = 1000; + ul_bler_target_upper = 0.25; + ul_bler_target_lower = 0.15; ul_max_mcs = 25; } ); @@ -194,7 +196,7 @@ RUs = ( local_rf = "yes" nb_tx = 2; nb_rx = 2; - att_tx = 15; + att_tx = 0; att_rx = 0; bands = [77]; max_pdschReferenceSignalPower = -27; diff --git a/ci-scripts/xml_files/container_sa_n310_2X2_100MHz_quectel.xml b/ci-scripts/xml_files/container_sa_n310_2X2_100MHz_quectel.xml index 6823b553c4..98c20a5a6e 100644 --- a/ci-scripts/xml_files/container_sa_n310_2X2_100MHz_quectel.xml +++ b/ci-scripts/xml_files/container_sa_n310_2X2_100MHz_quectel.xml @@ -124,7 +124,7 @@ matix ci-scripts/yaml_files/5g_sa_n310_2x2_100MHz - oai-gnb=RetxCheck=10,100,100,100 oai-gnb=EndsWithBye + oai-gnb=RetxCheck=dl=10,100,100,100;ul=25,100,100,100 oai-gnb=EndsWithBye From 22fdcd641a5a490f857976105c365c49de6b563b Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Mon, 16 Mar 2026 13:49:46 +0100 Subject: [PATCH 16/17] Print PUCCH SNR --- doc/MAC/mac-usage.md | 4 +++- openair2/LAYER2/NR_MAC_gNB/main.c | 6 +++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/MAC/mac-usage.md b/doc/MAC/mac-usage.md index 84150506c5..e0798bc6f7 100644 --- a/doc/MAC/mac-usage.md +++ b/doc/MAC/mac-usage.md @@ -81,7 +81,7 @@ Example: UE RNTI 2460 CU-UE-ID 2 in-sync PH 28 dB PCMAX 24 dBm, average RSRP -74 (8 meas), average SINR 40.0 (32 meas) UE 2460: CQI 15, RI 2, PMI (14,1) UE 2460: UL-RI 2 TPMI 0 -UE 2460: dlsch_rounds 32917/5113/1504/560, dlsch_errors 211, pucch0_DTX 1385, BLER 0.19557 MCS (1) 23 CCE fail 3 +UE 2460: dlsch_rounds 32917/5113/1504/560, dlsch_errors 211, pucch0_DTX 1385 (SNR 19.8+0.2 dB), BLER 0.19557 MCS (1) 23 CCE fail 3 UE 2460: ulsch_rounds 3756/353/182/179, ulsch_errors 170, ulsch_DTX 285, BLER 0.33021 MCS (1) 27 (Qm 8 dB) NPRB 5 SNR 31.0 (-1.0) dB CCE fail 0 UE 2460: MAC: TX 1530943191 RX 194148 bytes UE 2460: LCID 1: TX 651 RX 3031 bytes @@ -139,6 +139,8 @@ The fourth and fifth line show HARQ-related information: and ACK/NAK cannot be conveyed properly or DL DCIs are missed by the UE. This is also something that should be very small compared to `A` in `dlsch_rounds`. +* `(SNR x+y dB)`: PUCCH SNR where `x` is the average PUCCH SNR and `y` the + difference to the target (positive: above SNR, negative: below) * `DLSCH BLER` is the current measured block-error rate of the DLSCH. Basically a moving average of `B`/`A` in `dlsch_rounds`. This is something that should always be close to the target bler that the MAC scheduler uses. typically 10-30% if diff --git a/openair2/LAYER2/NR_MAC_gNB/main.c b/openair2/LAYER2/NR_MAC_gNB/main.c index babbeb5e47..f13014e408 100644 --- a/openair2/LAYER2/NR_MAC_gNB/main.c +++ b/openair2/LAYER2/NR_MAC_gNB/main.c @@ -197,11 +197,15 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset for (int i = 1; i < gNB->dl_bler.harq_round_max; i++) output = st_append(output, end, "/%"PRIu64, stats->dl.rounds[i]); + float pucch_snr = nr_mac_get_snr(&sched_ctrl->pucch_pc); + float pucch_snr_diff = (pucch_snr * 10.0f - sched_ctrl->pucch_pc.target_snrx10) / 10.0f; output = st_append(output, end, - ", dlsch_errors %"PRIu64", pucch0_DTX %d, BLER %.5f MCS (%d) %d CCE fail %d\n", + ", dlsch_errors %"PRIu64", pucch0_DTX %d (SNR %.1f%+.1f dB), BLER %.5f MCS (%d) %d CCE fail %d\n", stats->dl.errors, stats->pucch0_DTX, + pucch_snr, + pucch_snr_diff, sched_ctrl->dl_bler_stats.bler, UE->current_DL_BWP.mcsTableIdx, sched_ctrl->dl_bler_stats.mcs, From 1428aeac636a1565c8b61f0c6dd81518d9d710df Mon Sep 17 00:00:00 2001 From: Robert Schmidt Date: Tue, 24 Mar 2026 08:45:26 +0100 Subject: [PATCH 17/17] Add documentation for gNB power control, and explain deltaMCS --- doc/MAC/mac-usage.md | 41 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/doc/MAC/mac-usage.md b/doc/MAC/mac-usage.md index e0798bc6f7..a1fa90d9b6 100644 --- a/doc/MAC/mac-usage.md +++ b/doc/MAC/mac-usage.md @@ -67,6 +67,43 @@ happen. Say we have 0% PDCCH success rate (radio link failure scenario) but `pdcch_cl_adjust` is 0 indicating perfect PDCCH channel. it would take ~18 PDCCH failures to reach maximum aggregation level. +## Power control + +The gNB tracks the average SNR used by the UE for PUSCH/PUCCH, and sends TPC +commands to maintain the UE at a specific target SNR. Internally, it maintains +an average of measured SNR and RSSI. It also updates `tpc_in_flight`, which +tracks TPC changes that don't show up in the average yet. For instance, imagine +that the target SNR of 15 changes to 20. Three successive TPC commands need to +be sent (+3, +1, +1), but it will take time to show up in the average SNR. To +account for this, `tpc_in_flight` is updated by the TPCs sent, and an average +will make it go down back to zero at the same pace as the average SNR +approaches the target SNR. The sum of average SNR and `tpc_in_flight` sums up +to the actual, current SNR, which approximates the target SNR. The power +control tries to keep the SNR within -1<=targetSNR<=+2dB to avoid too many TPC +changes. On each DTX, `tpc_in_flight` is lowered by 1dB, correspondingly +lowering the current SNR by 1dB, which result in "boosting" the UE's target +SNR. + +For PUCCH, the gNB will try to keep the UE at the target SNR as configured by +`pucch_TargetSNRx10`. + +For PUSCH, two modes are available currently. Both have in common that +`pusch_TargetSNRx10` is used to configure a specific target SNR, but the +meaning of the target SNR changes depending on the mode. (For the following, +note that as per 38.213 Sec 7.1, the number of PRBs used for a PUSCH +transmission is always taken into account by the UE). + +1. the "normal" mode (default), in which the gNB tries to keep the UE at the + target SNR, regardless of MCS (and layers) used. This is the default. + Depending on the target MCS and number of layers, the target SNR should be + placed in the 20-30dB range. +2. the "deltaMCS" mode (`deltaMCS=1`, see below). In this mode, the UE accounts + for the MCS in the power used for PUSCH transmissions automatically (see + delta_TF factor in TS 38.213 Sec 7.1) when using only one layer. Thus it is + sufficient to set a lower target SNR (5-10dB). As the spec foresees that + this only works for one layer, it is suggested to disable SRS to disable + multi-layer operation in UL. + ## Periodic output and interpretation The scheduler periodically outputs statistics that can help you judge the radio @@ -277,9 +314,7 @@ configuration](../RRC/rrc-usage.md) as well for SIB configuration. * `disable_harq` (default 0=false): flag whether to disable HARQ completely (useful for NTN operation, see <../RUNMODEM.md>). **this is a Rel-17 feature and you need to have a capable UE for this** -* `use_deltaMCS` (default 0=false): flag whether to enable deltaMCS (**this is not fully tested - and might not work** and you might need to adjust other parameters such as - target SNRs) +* `use_deltaMCS` (default 0=false): flag whether to enable deltaMCS * `num_dlharq` (default 16): number of HARQ processes to use in DL (other valid options are 2, 4, 6, 8, 10, 12, 32; **32 is a Rel-17 features**) * `num_ulharq` (default 16): as `num_dlharq` for UL (other valid option is 32;