Refactor DL scheduler into staged pipeline with pluggable function pointers

Assisted By Claude Code:Opus-4.6

Signed-off-by: Maxime Elkael <m.elkael@northeastern.edu>
This commit is contained in:
Maxime
2026-05-18 15:01:15 -04:00
committed by Maxime Elkael
parent 6fc2d06077
commit 8ed54d7077
11 changed files with 1273 additions and 669 deletions

View File

@@ -1271,6 +1271,7 @@ set (MAC_NR_SRC
${NR_GNB_MAC_DIR}/gNB_scheduler.c
${NR_GNB_MAC_DIR}/gNB_scheduler_bch.c
${NR_GNB_MAC_DIR}/gNB_scheduler_dlsch.c
${NR_GNB_MAC_DIR}/gNB_scheduler_dlsch_default_policies.c
${NR_GNB_MAC_DIR}/gNB_scheduler_ulsch.c
${NR_GNB_MAC_DIR}/gNB_scheduler_primitives.c
${NR_GNB_MAC_DIR}/gNB_scheduler_phytest.c

View File

@@ -269,7 +269,22 @@ void nr_dlsim_preprocessor(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch)
AssertFatal(sched_pdsch.mcs >= 0, "invalid mcs %d\n", sched_pdsch.mcs);
AssertFatal(current_BWP->mcsTableIdx >= 0 && current_BWP->mcsTableIdx <= 2, "invalid mcsTableIdx %d\n", current_BWP->mcsTableIdx);
post_process_dlsch(nr_mac, pp_pdsch, UE_info, &sched_pdsch);
nr_dl_candidate_t candidate = {
.UE = UE_info,
.rnti = UE_info->rnti,
.is_retx = sched_ctrl->harq_processes[sched_pdsch.dl_harq_pid].round > 0,
.retx_harq_pid = sched_pdsch.dl_harq_pid,
.pending_bytes = sched_ctrl->num_total_bytes,
.mcs_table = current_BWP->mcsTableIdx,
.bwp_start = sched_pdsch.bwp_info.bwpStart,
.bwp_size = sched_pdsch.bwp_info.bwpSize,
};
for (int i = 0; i < seq_arr_size(&sched_ctrl->lc_config); ++i) {
const nr_lc_config_t *c = seq_arr_at(&sched_ctrl->lc_config, i);
candidate.pending_bytes_per_lcid[c->lcid] = sched_ctrl->rlc_status[c->lcid].bytes_in_buffer;
}
post_process_dlsch(nr_mac, pp_pdsch, UE_info, &sched_pdsch, &candidate);
}
nrUE_params_t nrUE_params;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,292 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
/*!
* \brief Default pluggable policy functions for the DL scheduler pipeline.
*
* These are the built-in implementations behind the function pointers
* (dl_ri_pmi_select, dl_tda_select, dl_beam_select, dl_mcs_select, dl_rb_alloc, dl_lcid_alloc)
* wired up at MAC init time. They can be replaced at runtime by external
* scheduler plug-ins without touching the core scheduling loop in
* gNB_scheduler_dlsch.c.
*/
#include "common/utils/nr/nr_common.h"
#include "gNB_scheduler_dlsch_default_policies.h"
/*MAC*/
#include "NR_MAC_COMMON/nr_mac.h"
#include "NR_MAC_gNB/nr_mac_gNB.h"
#include "LAYER2/NR_MAC_gNB/mac_proto.h"
#include "openair2/LAYER2/nr_rlc/nr_rlc_oai_api.h"
/*TAG*/
#include "NR_TAG-Id.h"
/*Softmodem params*/
#include "executables/softmodem-common.h"
#include "../../../nfapi/oai_integration/vendor_ext.h"
// Default RI/PMI selector: reads rank and PMI from CSI feedback for new-tx,
// or from HARQ process state for retx.
void nr_dl_ri_pmi_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates)
{
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
{
NR_UE_sched_ctrl_t *sched_ctrl = &cand->UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &cand->UE->current_DL_BWP;
if (cand->is_retx) {
cand->sched_pdsch.nrOfLayers = sched_ctrl->harq_processes[cand->retx_harq_pid].sched_pdsch.nrOfLayers;
cand->sched_pdsch.pm_index =
get_pm_index(mac, cand->UE, dl_bwp->dci_format, cand->sched_pdsch.nrOfLayers, mac->radio_config.pdsch_AntennaPorts.XP);
} else {
cand->sched_pdsch.nrOfLayers = cand->csi_ri + 1;
cand->sched_pdsch.pm_index = cand->csi_pm_index;
}
}
}
// Default TDA selector: picks the slot-wide TDA index from get_dl_tda(),
// then resolves tda_info per candidate using each UE's own BWP / search
// space / coreset. Marks invalids with skipped=true.
int nr_dl_tda_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates, frame_t frame, slot_t slot)
{
int tda = get_dl_tda(mac, slot);
AssertFatal(tda >= 0, "Unable to find PDSCH time domain allocation in list\n");
const NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon;
int n_valid = 0;
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
{
if (cand->skipped)
continue;
NR_UE_info_t *UE = cand->UE;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
int coresetid = sched_ctrl->coreset->controlResourceSetId;
NR_tda_info_t tda_info = get_dl_tda_info(dl_bwp,
sched_ctrl->search_space->searchSpaceType->present,
tda,
scc->dmrs_TypeA_Position,
1,
TYPE_C_RNTI_,
coresetid,
false);
if (!tda_info.valid_tda) {
cand->skipped = true;
continue;
}
/* For retransmissions with a changed TDA, refit rbSize to preserve TBS */
if (cand->is_retx) {
const NR_sched_pdsch_t *orig = &sched_ctrl->harq_processes[cand->retx_harq_pid].sched_pdsch;
bool tda_changed =
tda_info.startSymbolIndex != orig->tda_info.startSymbolIndex || tda_info.nrOfSymbols != orig->tda_info.nrOfSymbols;
if (tda_changed) {
uint16_t new_rbSize = check_dl_retx_feasibility(cand, tda, &tda_info, scc, dl_bwp->BWPSize);
if (!new_rbSize) {
cand->skipped = true;
continue;
}
cand->retx_rbSize = new_rbSize;
}
}
cand->sched_pdsch.time_domain_allocation = tda;
cand->sched_pdsch.tda_info = tda_info;
cand->alloc_slbitmap = SL_to_bitmap(tda_info.startSymbolIndex, tda_info.nrOfSymbols);
n_valid++;
}
return n_valid;
}
static int compare_dl_pf_ptrs(const void *a, const void *b)
{
const nr_dl_candidate_t *ca = *(const nr_dl_candidate_t *const *)a;
const nr_dl_candidate_t *cb = *(const nr_dl_candidate_t *const *)b;
/* retx first (INFINITY weight), then highest PF weight */
float wa = ca->is_retx ? INFINITY : dl_pf_weight(ca->current_mcs, ca->mcs_table, ca->sched_pdsch.nrOfLayers, ca->avg_throughput);
float wb = cb->is_retx ? INFINITY : dl_pf_weight(cb->current_mcs, cb->mcs_table, cb->sched_pdsch.nrOfLayers, cb->avg_throughput);
return (wa < wb) - (wa > wb);
}
int nr_dl_beam_select_default(NR_beam_info_t *beam_info,
const int16_t *beam_index_list,
nr_dl_candidate_t *candidates,
int n_candidates,
frame_t frame,
slot_t slot,
int slots_per_frame)
{
/* Build pointer array sorted by PF priority so retx and high-priority UEs claim beams first. */
nr_dl_candidate_t *order[MAX_MOBILES_PER_GNB];
int n_active = 0;
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
if (!cand->skipped)
order[n_active++] = cand;
qsort(order, n_active, sizeof(*order), compare_dl_pf_ptrs);
int n_valid = 0;
for (int i = 0; i < n_active; i++) {
nr_dl_candidate_t *cand = order[i];
NR_beam_alloc_t beam = beam_allocation_procedure(beam_info, frame, slot, cand->alloc_beam_dir, slots_per_frame);
if (beam.idx < 0) {
cand->skipped = true;
continue;
}
cand->alloc_beam_idx = beam.idx;
cand->alloc_new_beam = beam.new_beam;
n_valid++;
}
return n_valid;
}
void nr_dl_mcs_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates)
{
const NR_bler_options_t *bo = &mac->dl_bler;
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
{
int mcs;
if (cand->is_retx) {
mcs = cand->current_mcs; /* retx MCS is fixed by the HARQ round */
} else if (bo->harq_round_max == 1) {
mcs = max(bo->min_mcs, min(bo->max_mcs, cand->max_mcs));
} else if (!cand->bler_updated) {
mcs = cand->current_mcs;
} else {
mcs = nr_adapt_mcs_from_bler(cand->current_mcs,
bo->min_mcs,
cand->max_mcs,
cand->bler,
bo->lower,
bo->upper,
cand->last_num_sched);
}
cand->sched_pdsch.mcs = mcs;
/* Persist for all candidates — BLER-based MCS ramps even for UEs the
* policy rejects this slot (failed CCE, no free RBs, etc.). */
if (!cand->is_retx)
cand->UE->UE_sched_ctrl.dl_bler_stats.mcs = mcs;
}
}
static int compare_dl_pf_rb_ptrs(const void *a, const void *b)
{
const nr_dl_candidate_t *ca = *(const nr_dl_candidate_t *const *)a;
const nr_dl_candidate_t *cb = *(const nr_dl_candidate_t *const *)b;
/* retx first, then highest PF weight (uses sched_pdsch.mcs, which is set by mcs_select) */
float wa =
ca->is_retx ? INFINITY : dl_pf_weight(ca->sched_pdsch.mcs, ca->mcs_table, ca->sched_pdsch.nrOfLayers, ca->avg_throughput);
float wb =
cb->is_retx ? INFINITY : dl_pf_weight(cb->sched_pdsch.mcs, cb->mcs_table, cb->sched_pdsch.nrOfLayers, cb->avg_throughput);
return (wa < wb) - (wa > wb);
}
int nr_dl_proportional_fair(const nr_dl_sched_params_t *params, nr_dl_candidate_t *candidates, int n_candidates)
{
const int min_rbSize = 5;
int n_scheduled = 0;
/* Build pointer array sorted by PF priority (retx first, then highest weight) */
nr_dl_candidate_t *order[MAX_MOBILES_PER_GNB];
int n_active = 0;
FOR_EACH_CANDIDATE(cand, candidates, n_candidates)
if (!cand->skipped)
order[n_active++] = cand;
qsort(order, n_active, sizeof(*order), compare_dl_pf_rb_ptrs);
/* Phase 1: HARQ retransmissions (highest priority, exact RBs) */
for (int j = 0; j < n_active; j++) {
nr_dl_candidate_t *cand = order[j];
if (!cand->is_retx)
continue;
int needed_rbs = cand->retx_rbSize;
uint16_t *vrb_map = params->vrb_map[cand->alloc_beam_idx];
int rbStart, rbSize;
if (!get_rb_alloc(needed_rbs,
cand->bwp_size,
cand->bwp_start,
cand->bwp_size,
vrb_map,
cand->alloc_slbitmap,
&rbStart,
&rbSize))
continue;
COMMIT_ALLOC(params, cand, rbStart, needed_rbs, cand->sched_pdsch.mcs, n_scheduled);
}
/* Phase 2: No-data UEs (TA command or beam switch MAC CE, no RLC data) */
for (int j = 0; j < n_active; j++) {
nr_dl_candidate_t *cand = order[j];
if (cand->is_retx || cand->pending_bytes > 0)
continue;
uint16_t *vrb_map = params->vrb_map[cand->alloc_beam_idx];
int rbStart, rbSize;
if (!get_rb_alloc(min_rbSize,
cand->bwp_size,
cand->bwp_start,
cand->bwp_size,
vrb_map,
cand->alloc_slbitmap,
&rbStart,
&rbSize))
continue;
COMMIT_ALLOC(params, cand, rbStart, min_rbSize, cand->sched_pdsch.mcs, n_scheduled);
}
/* Phase 3: New data UEs — PF priority order, largest free block */
for (int j = 0; j < n_active; j++) {
nr_dl_candidate_t *cand = order[j];
if (cand->is_retx || cand->pending_bytes == 0)
continue;
int rbStart;
uint16_t *vrb_map = params->vrb_map[cand->alloc_beam_idx];
int max_rbSize = find_largest_free_block(vrb_map, cand->alloc_slbitmap, cand->bwp_start, cand->bwp_size, &rbStart);
if (max_rbSize < min_rbSize)
continue;
int mcs = cand->sched_pdsch.mcs;
uint8_t Qm = nr_get_Qm_dl(mcs, cand->mcs_table);
uint16_t R = nr_get_code_rate_dl(mcs, cand->mcs_table);
NR_pdsch_dmrs_t dmrs = get_dl_dmrs_params(params->mac->common_channels->ServingCellConfigCommon,
&cand->UE->current_DL_BWP,
&cand->sched_pdsch.tda_info,
cand->sched_pdsch.nrOfLayers);
const int oh = 3 * 4 + (cand->UE->UE_sched_ctrl.ta_apply ? 2 : 0);
uint32_t tbs;
uint16_t rbSize;
nr_find_nb_rb(Qm,
R,
1,
cand->sched_pdsch.nrOfLayers,
cand->sched_pdsch.tda_info.nrOfSymbols,
dmrs.N_PRB_DMRS * dmrs.N_DMRS_SLOT,
cand->pending_bytes + oh,
min_rbSize,
max_rbSize,
&tbs,
&rbSize);
COMMIT_ALLOC(params, cand, rbStart, rbSize, mcs, n_scheduled);
}
return n_scheduled;
}
void nr_dl_lcid_alloc_default(const gNB_MAC_INST *mac,
const nr_dl_candidate_t *candidate,
int tbs_available,
int lcid_alloc[NR_MAX_NUM_LCID])
{
(void)mac;
(void)tbs_available;
memset(lcid_alloc, 0, NR_MAX_NUM_LCID * sizeof(int));
for (int lcid = 0; lcid < NR_MAX_NUM_LCID; lcid++)
lcid_alloc[lcid] = candidate->pending_bytes_per_lcid[lcid];
}

View File

@@ -0,0 +1,30 @@
/*
* SPDX-License-Identifier: LicenseRef-CSSL-1.0
*/
#ifndef GNB_SCHEDULER_DLSCH_DEFAULT_POLICIES_H
#define GNB_SCHEDULER_DLSCH_DEFAULT_POLICIES_H
#include "LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
void nr_dl_ri_pmi_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates);
void nr_dl_mcs_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates);
int nr_dl_beam_select_default(NR_beam_info_t *beam_info,
const int16_t *beam_index_list,
nr_dl_candidate_t *candidates,
int n_candidates,
frame_t frame,
slot_t slot,
int slots_per_frame);
int nr_dl_tda_select_default(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates, frame_t frame, slot_t slot);
int nr_dl_proportional_fair(const nr_dl_sched_params_t *params, nr_dl_candidate_t *candidates, int n_candidates);
void nr_dl_lcid_alloc_default(const gNB_MAC_INST *mac,
const nr_dl_candidate_t *candidate,
int tbs_available,
int lcid_alloc[NR_MAX_NUM_LCID]);
#endif /* GNB_SCHEDULER_DLSCH_DEFAULT_POLICIES_H */

View File

@@ -50,6 +50,8 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)
if (!is_xlsch_in_slot(dlsch_slot_bitmap, dlsch_slot_modval, slot_period))
return;
NR_UE_info_t *UE = mac->UE_info.connected_ue_list[0];
if (UE == NULL)
return;
NR_ServingCellConfigCommon_t *scc = mac->common_channels[0].ServingCellConfigCommon;
NR_UE_sched_ctrl_t *sched_ctrl = &UE->UE_sched_ctrl;
NR_UE_DL_BWP_t *dl_bwp = &UE->current_DL_BWP;
@@ -176,7 +178,19 @@ void nr_preprocessor_phytest(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch)
target_dl_Nl)
>> 3;
post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch);
nr_dl_candidate_t candidate = {
.UE = UE,
.rnti = rnti,
.is_retx = sched_pdsch.dl_harq_pid >= 0,
.retx_harq_pid = sched_pdsch.dl_harq_pid,
.pending_bytes = sched_ctrl->num_total_bytes,
.mcs_table = dl_bwp->mcsTableIdx,
.bwp_start = sched_pdsch.bwp_info.bwpStart,
.bwp_size = sched_pdsch.bwp_info.bwpSize,
};
candidate.pending_bytes_per_lcid[lcid] = sched_ctrl->rlc_status[lcid].bytes_in_buffer;
post_process_dlsch(mac, pp_pdsch, UE, &sched_pdsch, &candidate);
/* mark the corresponding RBs as used */
for (int rb = 0; rb < sched_pdsch.rbSize; rb++)

View File

@@ -702,6 +702,34 @@ bool nr_find_nb_rb(uint16_t Qm,
return *tbs >= bytes && *nb_rb <= nb_rb_max;
}
// Find the largest contiguous block of free RBs in the VRB map.
// Returns the block size, or 0 if no free RB is found. out_start is set to the
// first RB of the largest block when the returned size is nonzero.
int find_largest_free_block(const uint16_t *vrb_map, uint16_t slbitmap, int bwp_start, int bwp_size, int *out_start)
{
int best_start = 0, best_len = 0;
int cur_start = 0, cur_len = 0;
for (int rb = 0; rb < bwp_size; rb++) {
if (!(vrb_map[rb + bwp_start] & slbitmap)) {
if (cur_len == 0)
cur_start = rb;
cur_len++;
} else {
if (cur_len > best_len) {
best_start = cur_start;
best_len = cur_len;
}
cur_len = 0;
}
}
if (cur_len > best_len) {
best_start = cur_start;
best_len = cur_len;
}
*out_start = best_start;
return best_len;
}
bool get_rb_alloc(int rbSize_min,
int rbSize_max,
int bwpStart,
@@ -811,41 +839,44 @@ NR_pusch_dmrs_t get_ul_dmrs_params(const NR_ServingCellConfigCommon_t *scc,
#define BLER_UPDATE_FRAME 10
#define BLER_FILTER 0.9f
int get_mcs_from_bler(const NR_bler_options_t *bler_options,
int nr_adapt_mcs_from_bler(int current_mcs, int min_mcs, int max_mcs, float bler, float bler_lower, float bler_upper, int num_sched)
{
int mcs = current_mcs;
if (bler < bler_lower && mcs < max_mcs && num_sched > 3)
mcs++;
else if (bler > bler_upper || num_sched <= 3) // above threshold or no activity
mcs--;
return max(min_mcs, min(mcs, max_mcs));
}
bool update_bler_stats(const NR_bler_options_t *bler_options,
const NR_mac_dir_stats_t *stats,
NR_bler_stats_t *bler_stats,
int max_mcs,
frame_t frame)
{
int diff = frame - bler_stats->last_frame;
if (diff < 0) // wrap around
diff += 1024;
max_mcs = min(max_mcs, bler_options->max_mcs);
const uint8_t old_mcs = min(bler_stats->mcs, max_mcs);
if (diff < BLER_UPDATE_FRAME)
return old_mcs; // no update
return false;
// last update is longer than x frames ago
const int num_dl_sched = (int)(stats->rounds[0] - bler_stats->rounds[0]);
const int num_dl_retx = (int)(stats->rounds[1] - bler_stats->rounds[1]);
const float bler_window = num_dl_sched > 0 ? (float) num_dl_retx / num_dl_sched : bler_stats->bler;
const float bler_window = num_dl_sched > 0 ? (float)num_dl_retx / num_dl_sched : bler_stats->bler;
bler_stats->bler = BLER_FILTER * bler_stats->bler + (1 - BLER_FILTER) * bler_window;
int new_mcs = old_mcs;
if (bler_stats->bler < bler_options->lower && old_mcs < max_mcs && num_dl_sched > 3)
new_mcs += 1;
else if (bler_stats->bler > bler_options->upper || num_dl_sched <= 3) // above threshold or no activity
new_mcs -= 1;
// else we are within threshold boundaries
new_mcs = max(new_mcs, bler_options->min_mcs);
bler_stats->last_frame = frame;
bler_stats->mcs = new_mcs;
bler_stats->last_num_sched = num_dl_sched;
memcpy(bler_stats->rounds, stats->rounds, sizeof(stats->rounds));
LOG_D(MAC, "frame %4d MCS %d -> %d (num_dl_sched %d, num_dl_retx %d, BLER wnd %.3f avg %.6f)\n",
frame, old_mcs, new_mcs, num_dl_sched, num_dl_retx, bler_window, bler_stats->bler);
return new_mcs;
LOG_D(MAC,
"frame %4d BLER update (num_sched %d, num_retx %d, BLER wnd %.3f avg %.6f)\n",
frame,
num_dl_sched,
num_dl_retx,
bler_window,
bler_stats->bler);
return true;
}
nfapi_nr_dl_dci_pdu_t *prepare_dci_pdu(nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu,
@@ -2989,6 +3020,8 @@ static void init_bler_stats(const NR_bler_options_t *bler_options, NR_bler_stats
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));
for (int i = 0; i < MAX_NUM_OF_SSB; i++)
UE->beam_rsrp[i] = UE->beam_sinr[i] = INT16_MIN;
UE->uid = uid_linear_allocator_new(uia);
UE->rnti = rnti;
UE->CellGroup = CellGroup;

View File

@@ -501,6 +501,10 @@ static void evaluate_sinr_report(NR_UE_info_t *UE,
sched_ctrl->dl_max_mcs = get_mcs_from_SINRx10(mcs_table, sinr_report->r[0].SINRx10, nrOfLayers);
LOG_D(MAC, "Reported SSB-SINR = %01f, dl_max_mcs %d\n", sinr_report->r[0].SINRx10 / 10.0, sched_ctrl->dl_max_mcs);
for (RSRP_report_t *r = sinr_report->r; r < sinr_report->r + sinr_report->nb; r++)
if (r->resource_id < MAX_NUM_OF_SSB)
UE->beam_sinr[r->resource_id] = r->SINRx10;
}
static void evaluate_rsrp_report(NR_UE_info_t *UE,
@@ -574,6 +578,10 @@ static void evaluate_rsrp_report(NR_UE_info_t *UE,
// including ssb rsrp in mac stats
stats->cumul_rsrp += rsrp_report->r[0].RSRP;
stats->num_rsrp_meas++;
for (RSRP_report_t *r = rsrp_report->r; r < rsrp_report->r + rsrp_report->nb; r++)
if (r->resource_id < MAX_NUM_OF_SSB)
UE->beam_rsrp[r->resource_id] = r->RSRP;
}
static void evaluate_cri_report(uint8_t *payload, uint8_t cri_bitlen, int cumul_bits, NR_UE_sched_ctrl_t *sched_ctrl)

View File

@@ -10,6 +10,7 @@
#define __LAYER2_NR_MAC_PROTO_H__
#include "LAYER2/NR_MAC_gNB/nr_mac_gNB.h"
#include "LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch_default_policies.h"
#include "NR_TAG-Id.h"
#include "common/ngran_types.h"
#include "openair2/LAYER2/nr_pdcp/nr_pdcp_configuration.h"
@@ -60,8 +61,8 @@ void nr_schedule_ue_spec(module_id_t module_id,
nfapi_nr_dl_tti_request_t *DL_req,
nfapi_nr_tx_data_request_t *TX_req);
/* \brief default DL preprocessor init routine, returns preprocessor to call */
nr_pp_impl_dl nr_init_dlsch_preprocessor();
/* \brief default DL preprocessor */
void nr_dlsch_preprocessor(gNB_MAC_INST *mac, post_process_pdsch_t *pp_pdsch);
void schedule_nr_sib1(module_id_t module_idP,
frame_t frameP,
@@ -425,6 +426,8 @@ bool nr_find_nb_rb(uint16_t Qm,
* \param rbSize_ptr Pointer returning the size of the found free block of RBs
* \return Indicates if a free block of RBs of the required size could be found and *rbStart_ptr and *rbSize_ptr are set accordingly
*/
int find_largest_free_block(const uint16_t *vrb_map, uint16_t slbitmap, int bwp_start, int bwp_size, int *out_start);
bool get_rb_alloc(int rbSize_min,
int rbSize_max,
int bwpStart,
@@ -434,12 +437,45 @@ bool get_rb_alloc(int rbSize_min,
int *rbStart_ptr,
int *rbSize_ptr);
int get_mcs_from_bler(const NR_bler_options_t *bler_options,
/* Scalar core of the BLER -> MCS adaptation rule. Single source of truth
* for the activity-guard threshold and the lower/upper hysteresis. */
int nr_adapt_mcs_from_bler(int current_mcs,
int min_mcs,
int max_mcs,
float bler,
float bler_lower,
float bler_upper,
int num_sched);
bool update_bler_stats(const NR_bler_options_t *bler_options,
const NR_mac_dir_stats_t *stats,
NR_bler_stats_t *bler_stats,
int max_mcs,
frame_t frame);
float dl_pf_weight(int mcs, int mcs_table, int nrOfLayers, float avg_throughput);
uint16_t check_dl_retx_feasibility(const nr_dl_candidate_t *cand,
int tda,
const NR_tda_info_t *tda_info,
const NR_ServingCellConfigCommon_t *scc,
uint16_t max_rbSize);
bool nr_dl_validate_cce_pucch(const nr_dl_sched_params_t *params, nr_dl_candidate_t *cand);
bool commit_alloc(const nr_dl_sched_params_t *params, nr_dl_candidate_t *cand);
// Use inside the policy loops: sets RB/MCS on candidate, validates CCE/PUCCH,
// marks scheduled; continues on failure, returns on max_num_ue.
#define COMMIT_ALLOC(params, cand, rb_start_, rb_size_, mcs_, n_sched) \
do { \
(cand)->sched_pdsch.rbStart = (rb_start_); \
(cand)->sched_pdsch.rbSize = (rb_size_); \
(cand)->sched_pdsch.mcs = (mcs_); \
if (!commit_alloc(params, cand)) \
continue; \
(cand)->scheduled = true; \
(n_sched)++; \
if ((n_sched) >= (params)->max_num_ue) \
return (n_sched); \
} while (0)
int ul_buffer_index(int frame, int slot, int slots_per_frame, int size);
void UL_tti_req_ahead_initialization(gNB_MAC_INST *gNB, int n, int CCid, frame_t frameP, int slotP);
uint64_t get_ssb_bitmap(const NR_ServingCellConfigCommon_t *scc);
@@ -495,7 +531,11 @@ void prepare_du_configuration_update(gNB_MAC_INST *mac,
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_dlsch(gNB_MAC_INST *nr_mac,
post_process_pdsch_t *pdsch,
NR_UE_info_t *UE,
NR_sched_pdsch_t *sched_pdsch,
const nr_dl_candidate_t *candidate);
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);

View File

@@ -173,7 +173,8 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset
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 (SNR %.1f%+.1f dB), 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, goodput %.2f Mbps\n",
stats->dl.errors,
stats->pucch0_DTX,
pucch_snr,
@@ -181,7 +182,8 @@ size_t dump_mac_stats(gNB_MAC_INST *gNB, char *output, size_t strlen, bool reset
sched_ctrl->dl_bler_stats.bler,
UE->current_DL_BWP.mcsTableIdx,
sched_ctrl->dl_bler_stats.mcs,
sched_ctrl->dl_cce_fail);
sched_ctrl->dl_cce_fail,
UE->dl_thr_ue_display / 1e6);
if (reset_rsrp) {
stats->num_rsrp_meas = 0;
stats->cumul_rsrp = 0;
@@ -300,12 +302,19 @@ void mac_top_init_gNB(ngran_node_t node_type,
uid_linear_allocator_init(&RC.nrmac[i]->UE_info.uid_allocator);
RC.nrmac[i]->dl_lcid_alloc = nr_dl_lcid_alloc_default;
if (get_softmodem_params()->phy_test) {
RC.nrmac[i]->pre_processor_dl = nr_preprocessor_phytest;
RC.nrmac[i]->pre_processor_ul = nr_ul_preprocessor_phytest;
} else {
RC.nrmac[i]->pre_processor_dl = nr_init_dlsch_preprocessor();
RC.nrmac[i]->pre_processor_dl = nr_dlsch_preprocessor;
RC.nrmac[i]->pre_processor_ul = nr_init_ulsch_preprocessor();
RC.nrmac[i]->dl_ri_pmi_select = nr_dl_ri_pmi_select_default;
RC.nrmac[i]->dl_mcs_select = nr_dl_mcs_select_default;
RC.nrmac[i]->dl_beam_select = nr_dl_beam_select_default;
RC.nrmac[i]->dl_tda_select = nr_dl_tda_select_default;
RC.nrmac[i]->dl_rb_alloc = nr_dl_proportional_fair;
}
if (!IS_SOFTMODEM_NOSTATS)
threadCreate(&RC.nrmac[i]->stats_thread,

View File

@@ -523,6 +523,7 @@ typedef struct NR_bler_stats {
float bler;
uint8_t mcs;
uint64_t rounds[8];
int last_num_sched; // scheduling count at last BLER update (for activity guard)
} NR_bler_stats_t;
//
@@ -714,6 +715,8 @@ typedef struct NR_mac_dir_stats {
uint32_t total_rbs_retx;
uint32_t num_mac_sdu;
uint32_t current_rbs;
uint64_t prev_sdu_bytes;
frame_t last_goodput_frame;
} NR_mac_dir_stats_t;
typedef struct NR_mac_stats {
@@ -784,8 +787,14 @@ typedef struct NR_UE_info {
measgap_config_t measgap_config;
// UE selected beam index
uint16_t UE_beam_index;
/// Per-SSB L1-RSRP table, indexed by SSB index (resource_id from CSI report).
/// INT16_MIN means no measurement received yet for that beam.
int16_t beam_rsrp[MAX_NUM_OF_SSB];
/// Per-SSB L1-SINR×10 table, same indexing. INT16_MIN means no data.
int16_t beam_sinr[MAX_NUM_OF_SSB];
float ul_thr_ue;
float dl_thr_ue;
float dl_thr_ue_display; ///< slow EWMA for stable display (alpha=0.001)
long pdsch_HARQ_ACK_Codebook;
bool is_redcap;
bool reestablish_rlc;
@@ -822,7 +831,8 @@ typedef struct {
nr_beam_mode_t beam_mode;
} NR_beam_info_t;
#define UE_iterator(BaSe, VaR) for (NR_UE_info_t **VaR##pptr=BaSe, *VaR=*VaR##pptr; VaR; VaR=*(++VaR##pptr))
#define UE_iterator(BaSe, VaR) for (NR_UE_info_t **VaR##pptr = BaSe, *VaR = *VaR##pptr; VaR; VaR = *(++VaR##pptr))
#define FOR_EACH_CANDIDATE(VaR, ArR, N) for (__typeof__(*(ArR)) *VaR = (ArR); VaR < (ArR) + (N); VaR++)
typedef struct {
/// current frame
@@ -846,6 +856,76 @@ typedef struct {
nfapi_nr_dl_tti_pdcch_pdu_rel15_t *pdcch_pdu_coreset[MAX_NUM_CORESET];
} post_process_pusch_t;
/* forward declarations for scheduling types */
struct gNB_MAC_INST_s;
typedef struct nr_dl_candidate nr_dl_candidate_t;
/// Scheduling context passed to the RB allocation policy.
/// Contains per-beam VRB maps so the policy can handle beam partitioning
/// internally (e.g. for MU-MIMO cross-beam scheduling).
typedef struct nr_dl_sched_params nr_dl_sched_params_t;
struct nr_dl_sched_params {
struct gNB_MAC_INST_s *mac; ///< MAC instance (for CCE/PUCCH validation)
int CC_id;
frame_t frame;
slot_t slot;
int num_beams; ///< number of beams
int max_num_ue; ///< max UEs to schedule
uint16_t *vrb_map[MAX_NUM_BEAM_PERIODS]; ///< per-beam VRB maps [275], mutable
int n_rb_avail[MAX_NUM_BEAM_PERIODS]; ///< available RBs per beam
int min_mcs; ///< minimum MCS from BLER config
float bler_lower; ///< BLER lower threshold (increase MCS if below)
float bler_upper; ///< BLER upper threshold (decrease MCS if above)
};
/// Per-UE scheduling candidate — read-only inputs for the policy function.
struct nr_dl_candidate {
/* ── UE identity / scheduling state (set by collect, never modified after) ── */
NR_UE_info_t *UE;
uint16_t rnti; ///< UE RNTI (convenience, avoids UE pointer dereference)
bool is_retx; ///< true = HARQ retransmission pending
int8_t retx_harq_pid; ///< HARQ PID for retx, -1 if none
int retx_rbSize; ///< RBs needed for retx, 0 for new tx
uint32_t pending_bytes; ///< total bytes waiting in RLC buffers
uint32_t pending_bytes_per_lcid[NR_MAX_NUM_LCID]; ///< per-LCID bytes waiting in RLC buffers
float avg_throughput; ///< EWMA goodput in bps (dl_thr_ue)
float bler; ///< current BLER estimate
int current_mcs; ///< current MCS state (retx: from HARQ, new tx: from BLER tracker)
int max_mcs; ///< max allowed MCS (config + UE capability)
int last_num_sched; ///< scheduled occasions in last BLER window
bool bler_updated; ///< true if BLER was refreshed this frame
int mcs_table; ///< MCS table index (from BWP config)
int bwp_start; ///< UE's BWP start
int bwp_size; ///< UE's BWP size
uint64_t fiveQI; ///< 5QI from first DRB's QoS config (0 if none)
int priority; ///< LC priority from first DRB (lower = higher priority, 0 if none)
nssai_t nssai; ///< slice/service type/differentiator from first DRB
bool skipped; ///< true if dropped by TDA/beam select (skip in downstream stages)
bool scheduled; ///< true if accepted by the RB-allocation policy
/* ── UE CSI observations (set by collect, read-only after) ─────────────── */
uint16_t cqi; ///< UE-reported wideband CQI
uint8_t csi_ri; ///< UE-reported rank indicator (0 = rank-1); forced to 0 for DCI 1_0
int csi_pm_index; ///< PM index derived from UE-reported PMI + antenna config
const int16_t *beam_rsrp; ///< per-SSB L1-RSRP; points into NR_UE_info_t::beam_rsrp. INT16_MIN = no data
const int16_t *beam_sinr; ///< per-SSB L1-SINR×10; same indexing. INT16_MIN = no data
/* ── gNB decisions (written by the named pipeline stage) ───────────────── */
/* Use NR_sched_pdsch_t for fields shared with HARQ/dispatch (mcs, rbStart,
* rbSize, nrOfLayers, pm_index, tda, tda_info, pucch_allocation).*/
NR_sched_pdsch_t sched_pdsch;
uint16_t alloc_slbitmap; ///< symbol bitmap derived from sched_pdsch.tda_info
/* dl_beam_select: beam selection */
int alloc_beam_dir; ///< beam direction index (initialised from UE->UE_beam_index; may be overridden)
int alloc_beam_idx; ///< hardware beam structure index, set by beam_allocation_procedure
bool alloc_new_beam; ///< true if beam alloc claimed a fresh slot (release via reset_beam_status if unscheduled)
/* commit_alloc: CCE/PUCCH validation */
int alloc_cce_index; ///< CCE index for PDCCH
int alloc_aggregation_level; ///< PDCCH aggregation level
NR_sched_pdcch_t alloc_sched_pdcch; ///< PDCCH scheduling info
};
/* forward declaration to use in nr_pp_impl_dl */
struct gNB_MAC_INST_s;
typedef struct gNB_MAC_INST_s gNB_MAC_INST;
@@ -853,7 +933,49 @@ typedef struct gNB_MAC_INST_s gNB_MAC_INST;
typedef void (*nr_pp_impl_dl)(gNB_MAC_INST *nr_mac, post_process_pdsch_t *pp_pdsch);
typedef void (*nr_pp_impl_ul)(gNB_MAC_INST *nr_mac, post_process_pusch_t *pp_pusch);
typedef struct {
/// RI/PMI selection: sets nrOfLayers and pm_index per candidate from CSI feedback.
/// For retransmissions, nrOfLayers must match the original transmission.
/// Custom implementations may use SRS reciprocity to override the UE's reported RI/PMI.
typedef void (*nr_dl_ri_pmi_select_fn)(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates);
/// MCS adaptation: sets sched_pdsch.mcs from BLER state for every candidate.
/// Called for all candidates (including those that won't get scheduled) so
/// BLER-based MCS ramps even for UEs that fail CCE. Also persists the
/// decision to dl_bler_stats.mcs for continuity across slots.
typedef void (*nr_dl_mcs_select_fn)(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates);
/// Beam allocation: assigns beam structure index to each candidate.
/// beam_index_list maps SSB id -> beam index (same as mac->beam_index_list);
/// custom implementations can use it with cand->beam_rsrp[] to pick the best beam.
typedef int (*nr_dl_beam_select_fn)(NR_beam_info_t *beam_info,
const int16_t *beam_index_list,
nr_dl_candidate_t *candidates,
int n_candidates,
frame_t frame,
slot_t slot,
int slots_per_frame);
/// TDA selection: assigns tda/tda_info/slbitmap per candidate.
/// Returns the number of candidates with a valid TDA (compacts invalids out).
typedef int (
*nr_dl_tda_select_fn)(const gNB_MAC_INST *mac, nr_dl_candidate_t *candidates, int n_candidates, frame_t frame, slot_t slot);
/// Scheduling policy: decides PRB + MCS for candidates across all beams.
/// The beam loop is inside the policy so it can do cross-beam scheduling (e.g. MU-MIMO).
/// Writes sched_pdsch.rbStart/rbSize/mcs and cce/pucch fields on scheduled candidates.
/// Sets candidate->scheduled = true for each accepted UE; returns the count.
typedef int (*nr_dl_rb_alloc_fn)(const nr_dl_sched_params_t *params, nr_dl_candidate_t *candidates, int n_candidates);
/// Per-LCID byte allocation: decides how many bytes each LCID gets within
/// the available TBS for an initial transmission. Called during MAC PDU
/// generation. Writes lcid_alloc[lcid] = max data bytes for that LCID.
/// The execution loop caps actual RLC requests to these budgets.
typedef void (*nr_dl_lcid_alloc_fn)(const gNB_MAC_INST *mac,
const nr_dl_candidate_t *candidate,
int tbs_available,
int lcid_alloc[NR_MAX_NUM_LCID]);
typedef struct f1_config_t {
f1ap_setup_req_t *setup_req;
f1ap_setup_resp_t *setup_resp;
uint32_t gnb_id; // associated gNB's ID, not used in DU itself
@@ -962,6 +1084,17 @@ typedef struct gNB_MAC_INST_s {
/// UL preprocessor for differentiated scheduling
nr_pp_impl_ul pre_processor_ul;
/// DL scheduling pipeline function pointers
nr_dl_ri_pmi_select_fn dl_ri_pmi_select;
nr_dl_tda_select_fn dl_tda_select;
nr_dl_beam_select_fn dl_beam_select;
nr_dl_mcs_select_fn dl_mcs_select;
nr_dl_rb_alloc_fn dl_rb_alloc;
nr_dl_lcid_alloc_fn dl_lcid_alloc;
/// Optional state persistence for scheduling policies.
void *sched_stateful_data;
nr_mac_config_t radio_config;
nr_rlc_configuration_t rlc_config;
@@ -977,7 +1110,7 @@ typedef struct gNB_MAC_INST_s {
uint16_t min_grant_prb;
bool identity_pm;
int precoding_matrix_size[NR_MAX_NB_LAYERS];
int beam_index_list[MAX_NUM_OF_SSB];
int16_t beam_index_list[MAX_NUM_OF_SSB];
NR_sched_pdsch_t sib1_pdsch[MAX_NUM_OF_SSB];
/// dedicate UL TDA, common for all UEs