From f8178cede97b0d578d3fb24f93fc5d1657d10b7a Mon Sep 17 00:00:00 2001 From: makigo Date: Thu, 30 Apr 2026 08:27:47 +0900 Subject: [PATCH] mac: Phase 4 - cap MU-MIMO group size to active TX antenna count Reads mac->antenna_state to count active TX ports via __builtin_popcount(nr_get_tx_antenna_bitmask). Caps the partner-search while-loop bound to (active_tx - 1), so: active_tx = 4 -> up to 4-way MU-MIMO (3 partners) active_tx = 3 -> up to 3-way (2 partners) active_tx = 2 -> up to 2-way (1 partner) active_tx = 1 -> SU-MIMO only (0 partners) This couples antenna muting (the existing PHY-side socket control on port 9999) to spatial parallelism in the MU-MIMO scheduler. Muting a chain reduces the codebook's orthogonal codeword count, which is the right semantic for our antenna-muting research: muting reduces the number of UEs co-scheduled per slot, not the per-UE rate. Verified runtime behavior on rfsim (30s windows per state): TX mask 0x0F: 4-way 952 3-way 484 2-way 333 (full 4-TX) TX mask 0x03: 4-way 2 3-way 8 2-way 5935 (2-TX -> 2-way) TX mask 0x01: 4-way 0 3-way 0 2-way 11 (1-TX -> SU-MIMO) Antenna-control commands tested via existing test interface: echo "SET TX MASK 0x03" | nc -w 2 localhost 9999 --- .../LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c index 6bee9cad9a..6b86c8bfed 100644 --- a/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c +++ b/openair2/LAYER2/NR_MAC_gNB/gNB_scheduler_dlsch.c @@ -955,7 +955,23 @@ static void pf_dl(gNB_MAC_INST *mac, int partner_allocs[MAX_MU_MIMO_PARTNERS] = {-1, -1, -1}; int n_partners = 0; - if (mumimo_enabled() && remainUEs[beam.idx] > 0) { + /* nakaolab Phase 4: cap MU-MIMO group size to the active TX antenna count. + * When the antenna controller mutes some TX chains (e.g., 4->2 or 4->1), the + * effective number of orthogonal codewords in the codebook shrinks. Each + * co-scheduled UE consumes one spatial layer = one TX port. So: + * active_tx = 4 -> up to 4 UEs (3 partners), 4-way MU-MIMO + * active_tx = 2 -> up to 2 UEs (1 partner), 2-way MU-MIMO + * active_tx = 1 -> SU-MIMO only (0 partners) */ + int max_partners_now = MAX_MU_MIMO_PARTNERS; + if (mac->antenna_state) { + const uint8_t tx_mask = nr_get_tx_antenna_bitmask(mac->antenna_state); + int active_tx = __builtin_popcount(tx_mask); + if (active_tx < 1) active_tx = 1; + if (active_tx - 1 < max_partners_now) + max_partners_now = active_tx - 1; + } + + if (mumimo_enabled() && remainUEs[beam.idx] > 0 && max_partners_now > 0) { const uint8_t leader_x2 = iterator->UE->UE_sched_ctrl.CSI_report.cri_ri_li_pmi_cqi_report.pmi_x2; uint8_t classes_used = 1 << leader_x2; static int diag_count = 0; @@ -970,8 +986,9 @@ static void pf_dl(gNB_MAC_INST *mac, LOG_W(NR_MAC, "[mumimo-diag] %4d.%2d ENTER partner-search after RNTI %04x leader_x2=%d; eligible_others=%d\n", frame, slot, iterator->UE->rnti, leader_x2, n_cand); } - /* Find up to MAX_MU_MIMO_PARTNERS partners, one per remaining x2 class. */ - while (n_partners < MAX_MU_MIMO_PARTNERS && remainUEs[beam.idx] > 0) { + /* Find up to max_partners_now partners, one per remaining x2 class. + * max_partners_now is capped by active TX antenna count (Phase 4). */ + while (n_partners < max_partners_now && remainUEs[beam.idx] > 0) { NR_UE_info_t *new_partner = NULL; int new_CCE = -1; int new_alloc = -1;