Refactor DLSCH/ULSCH scheduler: extract proportional fair policy behind function pointer interface
DL/UL Scheduler Refactor: Staged Pipeline with Pluggable Function Pointers
Motivation
The current pf_dl() is a ~600-line monolithic function that mixes infrastructure concerns (UE iteration, HARQ management, CCE allocation, MAC PDU generation) with the scheduling policy (PF priority, RB allocation, MCS selection). This makes it hard to modify the scheduling strategy, test alternatives, or offload scheduling to a GPU (cuMAC).
This MR refactors the DL and UL schedulers into a clean separation between infrastructure and policy, using function pointers for beam allocation and scheduling decisions. The data structures (nr_dl_candidate_t, nr_dl_sched_params_t) are also designed to map directly to cuMAC's cumacCellGrpUeStatus/cumacSchdSol/CumacCellGrpPrms, to make future integration smooth.
Changes
Goodput tracking fix
dl_thr_ue now tracks actual goodput in bps (EWMA of SDU byte deltas per frame) instead of accumulating raw byte counts per slot, which are not as straightforward to interpret (due to variations in TDD patterns mostly). The new calculation matches closely with the throughput measured with e.g. iperf.
Helper extraction from pf_dl() and post_process_dlsch()
find_first_available_rbs()— first-fit contiguous RB allocationsetup_dl_harq_process()— HARQ process managementgenerate_dl_mac_pdu()— MAC CE + RLC data + paddingfill_dl_tx_request()— FAPI TX_req filling
New scheduling interface
nr_dl_candidate_t— per-UE flat struct with all immutable inputs (buffer status, BLER, MCS limits, beam, BWP) and outputs (scheduled, rbStart, rbSize, MCS). Kept relatively minimal for now, but it should be easy to add more input metrics in the future.nr_dl_sched_params_t— per-beam cell-level context (VRB map, available RBs, slot bitmap)
Function pointers (DL)
| Pointer | Default implementation | Role |
|---|---|---|
dl_ri_pmi_select |
nr_dl_ri_pmi_select_default |
Rank/PMI selection |
dl_beam_select |
nr_dl_beam_select_default |
Beam direction assignment |
dl_tda_select |
nr_dl_tda_select_default |
Time-domain allocation |
dl_mcs_select |
nr_dl_mcs_select_default |
MCS from BLER/SINR |
dl_rb_alloc |
nr_dl_proportional_fair |
PRB allocation (PF policy) |
Function pointers (UL)
| Pointer | Default implementation | Role |
|---|---|---|
ul_ri_tpmi_select |
nr_ul_ri_tpmi_select_default |
Rank/TPMI from SRS feedback |
ul_beam_select |
nr_ul_beam_select_default |
Beam direction assignment |
ul_tda_select |
nr_ul_tda_select_default |
Time-domain allocation |
ul_mcs_select |
nr_ul_mcs_select_default |
MCS from BLER/SINR |
ul_rb_alloc |
nr_ul_proportional_fair |
PRB allocation (retx first, then PF-sorted new-tx) |
All default implementations are in gNB_scheduler_dlsch.c (DL) and gNB_scheduler_ulsch.c (UL).
MCS selection flow
The old get_mcs_from_bler() entangled two concerns: updating the BLER estimate from HARQ feedback and deciding the MCS. These are now split:
- BLER tracking is infrastructure's job:
collect_dl_candidates()callsupdate_dl_bler_stats()which updates the BLER estimate from HARQ round statistics. - MCS selection is the policy's job: the proportional fair policy calls
select_mcs_from_bler()internally to adapt MCS based on the BLER value. A different policy could use an entirely different MCS strategy (e.g. cuMAC has its ownmcsSelectionLUT+ OLLA, one could decide to opportunistically lower the MCS while increasing the PRB allocation for reliability in some cases, etc).
For retransmissions, MCS/number of PRBs are passed as hints so the policy can use them as-is if desired, but we don't enforce it (adaptive HARQ possible too: the standard requires us to maintain TBS but in theory it could be achieved via changing the MCS and number of RBs if we wanted to).
Refactored flow
nr_dl_schedule() (formerly pf_dl()):
collect_dl_candidates() → build candidate array from UE list
schedule_dl_ues() → beam alloc + per-beam policy calls
for each scheduled candidate → CCE/PUCCH/TBS validation + post_processBeam allocation and scheduling policy are two separate function pointers, allowing each to be developed and tested independently (with the goal in the future to add a parameter in the config file for each, and telnet commands to hotswap).
schedule_dl_ues() wraps both into a single function: it first calls beam allocation to assign candidates to beams, then iterates over beams and calls the scheduling policy for each one. cuMAC performs joint beam + PRB allocation on the GPU, so when integrating later it will replace schedule_dl_ues().
Future work
- Channel matrix H from SRS on candidates for beam-aware scheduling
- Per-RB channel magnitude derived from SRS on candidates
- Config file parameters and telnet commands for hotswapping policies
- cuMAC integration via
schedule_dl_ues()replacement